pgzero-plus 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pgzero-plus might be problematic. Click here for more details.

pgzero/ptext.py ADDED
@@ -0,0 +1,500 @@
1
+ """pygame-text - high-level text rendering with Pygame.
2
+
3
+ This module is directly copied from
4
+
5
+ https://github.com/cosmologicon/pygame-text
6
+
7
+ at revision c04e59b7382a832e117f0598cdcbc1bb3eb26db5
8
+ and used under CC0.
9
+
10
+ """
11
+ # ptext module: place this in your import directory.
12
+
13
+ # ptext.draw(text, pos=None, **options)
14
+
15
+ # Please see README.md for explanation of options.
16
+ # https://github.com/cosmologicon/pygame-text
17
+
18
+ # flake8: noqa: E501
19
+ from __future__ import division
20
+
21
+ from math import ceil, sin, cos, radians
22
+ import pygame
23
+
24
+ DEFAULT_FONT_SIZE = 24
25
+ REFERENCE_FONT_SIZE = 100
26
+ DEFAULT_LINE_HEIGHT = 1.0
27
+ DEFAULT_FONT_NAME = None
28
+ FONT_NAME_TEMPLATE = "%s"
29
+ DEFAULT_COLOR = "white"
30
+ DEFAULT_BACKGROUND = None
31
+ DEFAULT_OUTLINE_COLOR = "black"
32
+ DEFAULT_SHADOW_COLOR = "black"
33
+ OUTLINE_UNIT = 1 / 24
34
+ SHADOW_UNIT = 1 / 18
35
+ DEFAULT_ALIGN = "left" # left, center, or right
36
+ DEFAULT_ANCHOR = 0, 0 # 0, 0 = top left ; 1, 1 = bottom right
37
+ DEFAULT_STRIP = True
38
+ ALPHA_RESOLUTION = 16
39
+ ANGLE_RESOLUTION_DEGREES = 3
40
+
41
+ AUTO_CLEAN = True
42
+ MEMORY_LIMIT_MB = 64
43
+ MEMORY_REDUCTION_FACTOR = 0.5
44
+
45
+ pygame.font.init()
46
+
47
+ _font_cache = {}
48
+
49
+
50
+ def getfont(fontname=None, fontsize=None, sysfontname=None,
51
+ bold=None, italic=None, underline=None):
52
+ if fontname is not None and sysfontname is not None:
53
+ raise ValueError("Can't set both fontname and sysfontname")
54
+ if fontname is None and sysfontname is None:
55
+ fontname = DEFAULT_FONT_NAME
56
+ if fontsize is None:
57
+ fontsize = DEFAULT_FONT_SIZE
58
+ key = fontname, fontsize, sysfontname, bold, italic, underline
59
+ if key in _font_cache:
60
+ return _font_cache[key]
61
+ if sysfontname is not None:
62
+ font = pygame.font.SysFont(
63
+ sysfontname, fontsize, bold or False, italic or False)
64
+ else:
65
+ if fontname is not None:
66
+ fontname = FONT_NAME_TEMPLATE % fontname
67
+ try:
68
+ font = pygame.font.Font(fontname, fontsize)
69
+ except IOError:
70
+ raise IOError("unable to read font filename: %s" % fontname)
71
+ if bold is not None:
72
+ font.set_bold(bold)
73
+ if italic is not None:
74
+ font.set_italic(italic)
75
+ if underline is not None:
76
+ font.set_underline(underline)
77
+ _font_cache[key] = font
78
+ return font
79
+
80
+
81
+ def wrap(text, fontname=None, fontsize=None, sysfontname=None,
82
+ bold=None, italic=None, underline=None, width=None, widthem=None, strip=None):
83
+ if widthem is None:
84
+ font = getfont(fontname, fontsize, sysfontname,
85
+ bold, italic, underline)
86
+ elif width is not None:
87
+ raise ValueError("Can't set both width and widthem")
88
+ else:
89
+ font = getfont(fontname, REFERENCE_FONT_SIZE,
90
+ sysfontname, bold, italic, underline)
91
+ width = widthem * REFERENCE_FONT_SIZE
92
+ if strip is None:
93
+ strip = DEFAULT_STRIP
94
+ texts = text.replace("\t", " ").split("\n")
95
+ lines = []
96
+ for text in texts:
97
+ if strip:
98
+ text = text.rstrip(" ")
99
+ if width is None:
100
+ lines.append(text)
101
+ continue
102
+ if not text:
103
+ lines.append("")
104
+ continue
105
+ # Preserve leading spaces in all cases.
106
+ a = len(text) - len(text.lstrip(" "))
107
+ # At any time, a is the rightmost known index you can legally split a line. I.e. it's legal
108
+ # to add text[:a] to lines, and line is what will be added to lines if
109
+ # text is split at a.
110
+ a = text.index(" ", a) if " " in text else len(text)
111
+ line = text[:a]
112
+ while a + 1 < len(text):
113
+ # b is the next legal place to break the line, with bline the
114
+ # corresponding line to add.
115
+ if " " not in text[a + 1:]:
116
+ b = len(text)
117
+ bline = text
118
+ elif strip:
119
+ # Lines may be split at any space character that immediately follows a non-space
120
+ # character.
121
+ b = text.index(" ", a + 1)
122
+ while text[b - 1] == " ":
123
+ if " " in text[b + 1:]:
124
+ b = text.index(" ", b + 1)
125
+ else:
126
+ b = len(text)
127
+ break
128
+ bline = text[:b]
129
+ else:
130
+ # Lines may be split at any space character, or any character immediately following
131
+ # a space character.
132
+ b = a + 1 if text[a] == " " else text.index(" ", a + 1)
133
+ bline = text[:b]
134
+ if font.size(bline)[0] <= width:
135
+ a, line = b, bline
136
+ else:
137
+ lines.append(line)
138
+ text = text[a:].lstrip(" ") if strip else text[a:]
139
+ a = text.index(" ", 1) if " " in text[1:] else len(text)
140
+ line = text[:a]
141
+ if text:
142
+ lines.append(line)
143
+ return lines
144
+
145
+
146
+ _fit_cache = {}
147
+
148
+
149
+ def _fitsize(text, fontname, sysfontname, bold, italic, underline, width, height, lineheight, strip):
150
+ key = text, fontname, sysfontname, bold, italic, underline, width, height, lineheight, strip
151
+ if key in _fit_cache:
152
+ return _fit_cache[key]
153
+
154
+ def fits(fontsize):
155
+ texts = wrap(text, fontname, fontsize, sysfontname,
156
+ bold, italic, underline, width, strip)
157
+ font = getfont(fontname, fontsize, sysfontname,
158
+ bold, italic, underline)
159
+ w = max(font.size(line)[0] for line in texts)
160
+ linesize = font.get_linesize() * lineheight
161
+ h = int(round((len(texts) - 1) * linesize)) + font.get_height()
162
+ return w <= width and h <= height
163
+ a, b = 1, 256
164
+ if not fits(a):
165
+ fontsize = a
166
+ elif fits(b):
167
+ fontsize = b
168
+ else:
169
+ while b - a > 1:
170
+ c = (a + b) // 2
171
+ if fits(c):
172
+ a = c
173
+ else:
174
+ b = c
175
+ fontsize = a
176
+ _fit_cache[key] = fontsize
177
+ return fontsize
178
+
179
+
180
+ def _resolvecolor(color, default):
181
+ if color is None:
182
+ color = default
183
+ if color is None:
184
+ return None
185
+ try:
186
+ return tuple(pygame.Color(color))
187
+ except ValueError:
188
+ return tuple(color)
189
+
190
+
191
+ def _resolvealpha(alpha):
192
+ if alpha >= 1:
193
+ return 1
194
+ return max(int(round(alpha * ALPHA_RESOLUTION)) / ALPHA_RESOLUTION, 0)
195
+
196
+
197
+ def _resolveangle(angle):
198
+ if not angle:
199
+ return 0
200
+ angle %= 360
201
+ return int(round(angle / ANGLE_RESOLUTION_DEGREES)) * ANGLE_RESOLUTION_DEGREES
202
+
203
+
204
+ # Return the set of points in the circle radius r, using Bresenham's
205
+ # circle algorithm
206
+ _circle_cache = {}
207
+
208
+
209
+ def _circlepoints(r):
210
+ r = int(round(r))
211
+ if r in _circle_cache:
212
+ return _circle_cache[r]
213
+ x, y, e = r, 0, 1 - r
214
+ _circle_cache[r] = points = []
215
+ while x >= y:
216
+ points.append((x, y))
217
+ y += 1
218
+ if e < 0:
219
+ e += 2 * y - 1
220
+ else:
221
+ x -= 1
222
+ e += 2 * (y - x) - 1
223
+ points += [(y, x) for x, y in points if x > y]
224
+ points += [(-x, y) for x, y in points if x]
225
+ points += [(x, -y) for x, y in points if y]
226
+ points.sort()
227
+ return points
228
+
229
+
230
+ _surf_cache = {}
231
+ _surf_tick_usage = {}
232
+ _surf_size_total = 0
233
+ _unrotated_size = {}
234
+ _tick = 0
235
+
236
+
237
+ def getsurf(text, fontname=None, fontsize=None, sysfontname=None, bold=None, italic=None,
238
+ underline=None, width=None, widthem=None, strip=None, color=None,
239
+ background=None, antialias=True, ocolor=None, owidth=None, scolor=None, shadow=None,
240
+ gcolor=None, alpha=1.0, align=None, lineheight=None, angle=0, cache=True):
241
+ global _tick, _surf_size_total
242
+ if fontname is None:
243
+ fontname = DEFAULT_FONT_NAME
244
+ if fontsize is None:
245
+ fontsize = DEFAULT_FONT_SIZE
246
+ fontsize = int(round(fontsize))
247
+ if align is None:
248
+ align = DEFAULT_ALIGN
249
+ if align in ["left", "center", "right"]:
250
+ align = [0, 0.5, 1][["left", "center", "right"].index(align)]
251
+ if lineheight is None:
252
+ lineheight = DEFAULT_LINE_HEIGHT
253
+ color = _resolvecolor(color, DEFAULT_COLOR)
254
+ background = _resolvecolor(background, DEFAULT_BACKGROUND)
255
+ gcolor = _resolvecolor(gcolor, None)
256
+ ocolor = None if owidth is None else _resolvecolor(
257
+ ocolor, DEFAULT_OUTLINE_COLOR)
258
+ scolor = None if shadow is None else _resolvecolor(
259
+ scolor, DEFAULT_SHADOW_COLOR)
260
+ opx = None if owidth is None else ceil(owidth * fontsize * OUTLINE_UNIT)
261
+ spx = None if shadow is None else tuple(
262
+ ceil(s * fontsize * SHADOW_UNIT) for s in shadow)
263
+ alpha = _resolvealpha(alpha)
264
+ angle = _resolveangle(angle)
265
+ strip = DEFAULT_STRIP if strip is None else strip
266
+ key = (text, fontname, fontsize, sysfontname, bold, italic, underline, width, widthem, strip,
267
+ color, background, antialias, ocolor, opx, scolor, spx, gcolor, alpha, align, lineheight, angle)
268
+ if key in _surf_cache:
269
+ _surf_tick_usage[key] = _tick
270
+ _tick += 1
271
+ return _surf_cache[key]
272
+ texts = wrap(text, fontname, fontsize, sysfontname, bold, italic, underline,
273
+ width=width, widthem=widthem, strip=strip)
274
+ if angle:
275
+ surf0 = getsurf(text, fontname, fontsize, sysfontname, bold, italic, underline,
276
+ width, widthem, strip, color, background, antialias,
277
+ ocolor, owidth, scolor, shadow, gcolor, alpha, align, lineheight, cache=cache)
278
+ if angle in (90, 180, 270):
279
+ surf = pygame.transform.rotate(surf0, angle)
280
+ else:
281
+ surf = pygame.transform.rotozoom(surf0, angle, 1.0)
282
+ _unrotated_size[(surf.get_size(), angle, text)] = surf0.get_size()
283
+ elif alpha < 1.0:
284
+ surf0 = getsurf(text, fontname, fontsize, sysfontname, bold, italic, underline,
285
+ width, widthem, strip, color, background, antialias,
286
+ ocolor, owidth, scolor, shadow, gcolor=gcolor, align=align,
287
+ lineheight=lineheight, cache=cache)
288
+ surf = surf0.copy()
289
+ array = pygame.surfarray.pixels_alpha(surf)
290
+ array[:, :] = (array[:, :] * alpha).astype(array.dtype)
291
+ del array
292
+ elif spx is not None:
293
+ surf0 = getsurf(text, fontname, fontsize, sysfontname, bold, italic, underline,
294
+ width, widthem, strip, color=color, background=(0, 0, 0, 0), antialias=antialias,
295
+ gcolor=gcolor, align=align, lineheight=lineheight, cache=cache)
296
+ ssurf = getsurf(text, fontname, fontsize, sysfontname, bold, italic, underline,
297
+ width, widthem, strip, color=scolor, background=(0, 0, 0, 0), antialias=antialias,
298
+ align=align, lineheight=lineheight, cache=cache)
299
+ w0, h0 = surf0.get_size()
300
+ sx, sy = spx
301
+ surf = pygame.Surface((w0 + abs(sx), h0 + abs(sy))).convert_alpha()
302
+ surf.fill(background or (0, 0, 0, 0))
303
+ dx, dy = max(sx, 0), max(sy, 0)
304
+ surf.blit(ssurf, (dx, dy))
305
+ x0, y0 = abs(sx) - dx, abs(sy) - dy
306
+ if len(color) > 3 and color[3] == 0:
307
+ array = pygame.surfarray.pixels_alpha(surf)
308
+ array0 = pygame.surfarray.pixels_alpha(surf0)
309
+ array[x0:x0 + w0, y0:y0 +
310
+ h0] -= array0.clip(max=array[x0:x0 + w0, y0:y0 + h0])
311
+ del array, array0
312
+ else:
313
+ surf.blit(surf0, (x0, y0))
314
+ elif opx is not None:
315
+ surf0 = getsurf(text, fontname, fontsize, sysfontname, bold, italic, underline,
316
+ width, widthem, strip, color=color, background=(0, 0, 0, 0), antialias=antialias,
317
+ gcolor=gcolor, align=align, lineheight=lineheight, cache=cache)
318
+ osurf = getsurf(text, fontname, fontsize, sysfontname, bold, italic, underline,
319
+ width, widthem, strip, color=ocolor, background=(0, 0, 0, 0), antialias=antialias,
320
+ align=align, lineheight=lineheight, cache=cache)
321
+ w0, h0 = surf0.get_size()
322
+ surf = pygame.Surface((w0 + 2 * opx, h0 + 2 * opx)).convert_alpha()
323
+ surf.fill(background or (0, 0, 0, 0))
324
+ for dx, dy in _circlepoints(opx):
325
+ surf.blit(osurf, (dx + opx, dy + opx))
326
+ if len(color) > 3 and color[3] == 0:
327
+ array = pygame.surfarray.pixels_alpha(surf)
328
+ array0 = pygame.surfarray.pixels_alpha(surf0)
329
+ array[opx:-opx, opx:-
330
+ opx] -= array0.clip(max=array[opx:-opx, opx:-opx])
331
+ del array, array0
332
+ else:
333
+ surf.blit(surf0, (opx, opx))
334
+ else:
335
+ font = getfont(fontname, fontsize, sysfontname,
336
+ bold, italic, underline)
337
+ # pygame.Font.render does not allow passing None as an argument value
338
+ # for background.
339
+ if background is None or (len(background) > 3 and background[3] == 0) or gcolor is not None:
340
+ lsurfs = [font.render(text, antialias, color).convert_alpha()
341
+ for text in texts]
342
+ else:
343
+ lsurfs = [font.render(text, antialias, color,
344
+ background).convert_alpha() for text in texts]
345
+ if gcolor is not None:
346
+ import numpy
347
+ for lsurf in lsurfs:
348
+ m = numpy.clip(numpy.arange(
349
+ lsurf.get_height()) * 2.0 / font.get_ascent() - 1.0, 0, 1)
350
+ array = pygame.surfarray.pixels3d(lsurf)
351
+ for j in (0, 1, 2):
352
+ array[:, :, j] = (
353
+ (1.0 - m) * array[:, :, j] + m * gcolor[j]).astype(array.dtype)
354
+ del array
355
+
356
+ if len(lsurfs) == 1 and gcolor is None:
357
+ surf = lsurfs[0]
358
+ else:
359
+ w = max(lsurf.get_width() for lsurf in lsurfs)
360
+ linesize = font.get_linesize() * lineheight
361
+ ys = [int(round(k * linesize)) for k in range(len(lsurfs))]
362
+ h = ys[-1] + font.get_height()
363
+ surf = pygame.Surface((w, h)).convert_alpha()
364
+ surf.fill(background or (0, 0, 0, 0))
365
+ for y, lsurf in zip(ys, lsurfs):
366
+ x = int(round(align * (w - lsurf.get_width())))
367
+ surf.blit(lsurf, (x, y))
368
+ if cache:
369
+ w, h = surf.get_size()
370
+ _surf_size_total += 4 * w * h
371
+ _surf_cache[key] = surf
372
+ _surf_tick_usage[key] = _tick
373
+ _tick += 1
374
+ return surf
375
+
376
+
377
+ _default_surf_sentinel = ()
378
+
379
+
380
+ def draw(text, pos=None,
381
+ fontname=None, fontsize=None, sysfontname=None,
382
+ antialias=True, bold=None, italic=None, underline=None,
383
+ color=None, background=None,
384
+ top=None, left=None, bottom=None, right=None,
385
+ topleft=None, bottomleft=None, topright=None, bottomright=None,
386
+ midtop=None, midleft=None, midbottom=None, midright=None,
387
+ center=None, centerx=None, centery=None,
388
+ width=None, widthem=None, lineheight=None, strip=None,
389
+ align=None,
390
+ owidth=None, ocolor=None,
391
+ shadow=None, scolor=None,
392
+ gcolor=None,
393
+ alpha=1.0,
394
+ anchor=None,
395
+ angle=0,
396
+ surf=_default_surf_sentinel,
397
+ cache=True):
398
+
399
+ if topleft:
400
+ left, top = topleft
401
+ if bottomleft:
402
+ left, bottom = bottomleft
403
+ if topright:
404
+ right, top = topright
405
+ if bottomright:
406
+ right, bottom = bottomright
407
+ if midtop:
408
+ centerx, top = midtop
409
+ if midleft:
410
+ left, centery = midleft
411
+ if midbottom:
412
+ centerx, bottom = midbottom
413
+ if midright:
414
+ right, centery = midright
415
+ if center:
416
+ centerx, centery = center
417
+
418
+ x, y = pos or (None, None)
419
+ hanchor, vanchor = anchor or (None, None)
420
+ if left is not None:
421
+ x, hanchor = left, 0
422
+ if centerx is not None:
423
+ x, hanchor = centerx, 0.5
424
+ if right is not None:
425
+ x, hanchor = right, 1
426
+ if top is not None:
427
+ y, vanchor = top, 0
428
+ if centery is not None:
429
+ y, vanchor = centery, 0.5
430
+ if bottom is not None:
431
+ y, vanchor = bottom, 1
432
+ if x is None:
433
+ raise ValueError("Unable to determine horizontal position")
434
+ if y is None:
435
+ raise ValueError("Unable to determine vertical position")
436
+
437
+ if align is None:
438
+ align = hanchor
439
+ if hanchor is None:
440
+ hanchor = DEFAULT_ANCHOR[0]
441
+ if vanchor is None:
442
+ vanchor = DEFAULT_ANCHOR[1]
443
+
444
+ tsurf = getsurf(text, fontname, fontsize, sysfontname, bold, italic, underline, width, widthem,
445
+ strip, color, background, antialias, ocolor, owidth, scolor, shadow, gcolor, alpha, align,
446
+ lineheight, angle, cache)
447
+ angle = _resolveangle(angle)
448
+ if angle:
449
+ w0, h0 = _unrotated_size[(tsurf.get_size(), angle, text)]
450
+ S, C = sin(radians(angle)), cos(radians(angle))
451
+ dx, dy = (0.5 - hanchor) * w0, (0.5 - vanchor) * h0
452
+ x += dx * C + dy * S - 0.5 * tsurf.get_width()
453
+ y += -dx * S + dy * C - 0.5 * tsurf.get_height()
454
+ else:
455
+ x -= hanchor * tsurf.get_width()
456
+ y -= vanchor * tsurf.get_height()
457
+ x = int(round(x))
458
+ y = int(round(y))
459
+
460
+ if surf is _default_surf_sentinel:
461
+ surf = pygame.display.get_surface()
462
+ if surf is not None:
463
+ surf.blit(tsurf, (x, y))
464
+
465
+ if AUTO_CLEAN:
466
+ clean()
467
+
468
+ return tsurf, (x, y)
469
+
470
+
471
+ def drawbox(text, rect, fontname=None, sysfontname=None, lineheight=None, anchor=None,
472
+ bold=None, italic=None, underline=None, strip=None, **kwargs):
473
+ if fontname is None:
474
+ fontname = DEFAULT_FONT_NAME
475
+ if lineheight is None:
476
+ lineheight = DEFAULT_LINE_HEIGHT
477
+ hanchor, vanchor = anchor = anchor or (0.5, 0.5)
478
+ rect = pygame.Rect(rect)
479
+ x = rect.x + hanchor * rect.width
480
+ y = rect.y + vanchor * rect.height
481
+ fontsize = _fitsize(text, fontname, sysfontname, bold, italic, underline,
482
+ rect.width, rect.height, lineheight, strip)
483
+ return draw(text, (x, y), fontname=fontname, fontsize=fontsize, lineheight=lineheight,
484
+ width=rect.width, strip=strip, anchor=anchor, **kwargs)
485
+
486
+
487
+ def clean():
488
+ global _surf_size_total
489
+ memory_limit = MEMORY_LIMIT_MB * (1 << 20)
490
+ if _surf_size_total < memory_limit:
491
+ return
492
+ memory_limit *= MEMORY_REDUCTION_FACTOR
493
+ keys = sorted(_surf_cache, key=_surf_tick_usage.get)
494
+ for key in keys:
495
+ w, h = _surf_cache[key].get_size()
496
+ del _surf_cache[key]
497
+ del _surf_tick_usage[key]
498
+ _surf_size_total -= 4 * w * h
499
+ if _surf_size_total < memory_limit:
500
+ break