| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
import pygame |
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
import pygame |
|---|
| 10 |
from pygame.locals import * |
|---|
| 11 |
|
|---|
| 12 |
class Font: |
|---|
| 13 |
""" |
|---|
| 14 |
A SFont file should be a normal picture file (PNGs work well though anything pygame can load can be used). It should contain, in order, these characters: |
|---|
| 15 |
! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ |
|---|
| 16 |
Leave an extra row of pixels at the top. In this row, between each character, place a string of pink=(255,0,255) pixels to demark the edges of the pixels |
|---|
| 17 |
There is one extra restriction |
|---|
| 18 |
Todo: |
|---|
| 19 |
grab all subsurfaces at font-creation time, not afterwards |
|---|
| 20 |
#XXX should return the length as second in the tuples? len is calc'd twice now, could cut it down) |
|---|
| 21 |
#XXX There's inefficiency: ranges() is run twice for every render() |
|---|
| 22 |
#XXX check this for bad-data bugs. Would it completely assrape if you don't give enough chars? or if accidentally connect two strings of pink together?. |
|---|
| 23 |
Support \n by moving down a line (perhaps split along \n and calculate based on how many lines you get?) |
|---|
| 24 |
|
|---|
| 25 |
Speed up tricks: |
|---|
| 26 |
+make a write() method that writes directly instead of rendering first |
|---|
| 27 |
+use numeric to do the wiping of the pixel-alphas |
|---|
| 28 |
+assume the colour in between chars is the colorkey; then just set_colorkey() of the rendering surface; but this would break any per-pixel alphas present... :(... perhaps check for per-pixel alphas first? |
|---|
| 29 |
Read SFont.c, in particular figure out this part: |
|---|
| 30 |
//#width = mid(self.CharPos[c+1]) - mid(self.CharPos[c])?? #get distance between the centers of this char & the next |
|---|
| 31 |
//#src.x = mid(self.CharPos[x]) #the center of the character |
|---|
| 32 |
//#dst.x = pos - mid(self.CharPos[x]) #pos is kept at the end of the place to write to; have to shift back 1 width.... except appearantly only half the character instead. |
|---|
| 33 |
srcrect.w = dstrect.w = |
|---|
| 34 |
(Font->CharPos[charoffset+2] + Font->CharPos[charoffset+1])/2 - |
|---|
| 35 |
(Font->CharPos[charoffset] + Font->CharPos[charoffset-1])/2; |
|---|
| 36 |
srcrect.x = (Font->CharPos[charoffset]+Font->CharPos[charoffset-1])/2; |
|---|
| 37 |
dstrect.x = x - (float)(Font->CharPos[charoffset] |
|---|
| 38 |
- Font->CharPos[charoffset-1])/2; |
|---|
| 39 |
|
|---|
| 40 |
SDL_BlitSurface(Font->Surface, &srcrect, Surface, &dstrect); |
|---|
| 41 |
""" |
|---|
| 42 |
def __init__(self, filename): |
|---|
| 43 |
"""Works like normal pygame.font.Font(): takes a file to load from and a size. Size is a bit broken so far, it just is the vertical size in pixels to scale all the letters to |
|---|
| 44 |
XXX Doesn't check if pygame is initialized! Will crash if it's not! |
|---|
| 45 |
Todo: wrap this so it falls back on self.font = pygame.font.SysFont("Times New Roman", 30) if it can't open? or would that be unpythonic? |
|---|
| 46 |
Todo: allow passing an initial-char arg so that it can cover a different section of chracters (e.g. cover some univocde) |
|---|
| 47 |
""" |
|---|
| 48 |
self.surface = pygame.image.load(filename) |
|---|
| 49 |
self.CharPos = [] |
|---|
| 50 |
|
|---|
| 51 |
""" |
|---|
| 52 |
algo: |
|---|
| 53 |
find a pink to the left of a non-pink, this marks the start of the letter |
|---|
| 54 |
find a pink to the right of a non-pink, this marks the end |
|---|
| 55 |
|
|---|
| 56 |
Equivilently: |
|---|
| 57 |
Loop to the right until you find a non-pink, this marks the start of the letter |
|---|
| 58 |
loop to the right until you find a pink, this marks the end |
|---|
| 59 |
Save the ranges |
|---|
| 60 |
""" |
|---|
| 61 |
PINK = self.surface.get_at((0,0)) |
|---|
| 62 |
|
|---|
| 63 |
|
|---|
| 64 |
pixels = enumerate(self.surface.get_at((i, 0)) for i in xrange(self.surface.get_width())) |
|---|
| 65 |
for i,pixel in pixels: |
|---|
| 66 |
|
|---|
| 67 |
if pixel == PINK: continue |
|---|
| 68 |
start = i |
|---|
| 69 |
|
|---|
| 70 |
while pixel != PINK: |
|---|
| 71 |
try: |
|---|
| 72 |
i,pixel = pixels.next() |
|---|
| 73 |
except StopIteration: break |
|---|
| 74 |
end = i |
|---|
| 75 |
self.CharPos.append((start,end)) |
|---|
| 76 |
|
|---|
| 77 |
|
|---|
| 78 |
def ranges(self, text): |
|---|
| 79 |
"Get a list of tuples representing the ranges needed to grab the chars out of self.surface for the given text" |
|---|
| 80 |
for c in text: |
|---|
| 81 |
c = ord(c)-33 |
|---|
| 82 |
if 0<=c<=len(self.CharPos): |
|---|
| 83 |
yield self.CharPos[c] |
|---|
| 84 |
else: |
|---|
| 85 |
|
|---|
| 86 |
yield (0,10) |
|---|
| 87 |
|
|---|
| 88 |
def size(self, text): |
|---|
| 89 |
"""Font.size(text) -> (width, height) |
|---|
| 90 |
|
|---|
| 91 |
Give the dimensions needed to draw the string in text |
|---|
| 92 |
""" |
|---|
| 93 |
width = 0 |
|---|
| 94 |
height = self.surface.get_height()-1 |
|---|
| 95 |
for range in self.ranges(text): |
|---|
| 96 |
width += range[1]-range[0] |
|---|
| 97 |
return width, height |
|---|
| 98 |
|
|---|
| 99 |
def write(self, surface, pos, text): |
|---|
| 100 |
"""Draw the text onto the surface at position pos=(x,y), just like in SFont.c |
|---|
| 101 |
|
|---|
| 102 |
I'm only putting this in here because of SFont... and because it is horribly slow |
|---|
| 103 |
otherwise due to the bottleneck in render(). |
|---|
| 104 |
But yeah, it doesn't fit the pygame model.""" |
|---|
| 105 |
size = self.size(text) |
|---|
| 106 |
loc = 0 |
|---|
| 107 |
for c in text: |
|---|
| 108 |
c = ord(c)-33 |
|---|
| 109 |
if 0<=c<=len(self.CharPos): |
|---|
| 110 |
range = self.CharPos[c] |
|---|
| 111 |
width = range[1]-range[0] |
|---|
| 112 |
char = self.surface.subsurface( (range[0], 1, width, size[1]) ) |
|---|
| 113 |
else: |
|---|
| 114 |
width = 10 |
|---|
| 115 |
char = pygame.Surface((width,size[1])) |
|---|
| 116 |
char.set_alpha(0) |
|---|
| 117 |
surface.blit(char, (pos[0]+loc,pos[1]+0)) |
|---|
| 118 |
loc+=width |
|---|
| 119 |
|
|---|
| 120 |
def render(self, text, antialias, background=(0,0,0,0)): |
|---|
| 121 |
"""Return a surface with the text rendered onto it using this font. Just like pygame.Font.render() |
|---|
| 122 |
If background is not given it defaults to being completely transparent. |
|---|
| 123 |
""" |
|---|
| 124 |
drawing = pygame.Surface(self.size(text)) |
|---|
| 125 |
drawing = drawing.convert(drawing.get_bitsize(), SRCALPHA) |
|---|
| 126 |
|
|---|
| 127 |
drawing.fill(background) |
|---|
| 128 |
|
|---|
| 129 |
self.write(drawing, (0,0), text) |
|---|
| 130 |
return drawing |
|---|
| 131 |
|
|---|
| 132 |
|
|---|
| 133 |
if __name__=='__main__': |
|---|
| 134 |
pygame.init() |
|---|
| 135 |
surf = pygame.display.set_mode((300,200)) |
|---|
| 136 |
surf.fill((255,0,255)) |
|---|
| 137 |
f = Font("24P_Arial_NeonYellow.png") |
|---|
| 138 |
|
|---|
| 139 |
surf.blit(f.render("fyadfyad~fyad!!"), (0,0)) |
|---|
| 140 |
pygame.display.flip() |
|---|
| 141 |
while 1: |
|---|
| 142 |
for e in pygame.event.get(): |
|---|
| 143 |
if e.type==pygame.QUIT: |
|---|
| 144 |
import sys |
|---|
| 145 |
sys.exit() |
|---|