File: wordle-cairo.py

package info (click to toggle)
freetype-py 2.5.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,976 kB
  • sloc: python: 7,676; makefile: 111
file content (180 lines) | stat: -rwxr-xr-x 6,364 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
#
#  pycairo/cairocffi-based wordle example - Copyright 2017 Hin-Tak Leung
#  Distributed under the terms of the new BSD license.
#
#  rewrite of the numply,matplotlib-based example from Nicolas P. Rougier
#  - Cairo can paint partly off-screen, so this example does!
#
#  This example behaves differently under pycairo 1.11+ (released in 2017-04-09).
#  Auto-checked. On older pycairo, it does not draw partial patterns at the edges.
#  Also, Suface.get_data() is not in the "python 3, pycairo < 1.11" combination.
#
# -----------------------------------------------------------------------------
from math import cos, sin
from numpy import random, sort, sqrt, ndarray, ubyte
from freetype import *

try:
    # pycairo 1.11+:
    from cairo import Region, RectangleInt, REGION_OVERLAP_OUT
except ImportError:
    # stubs for pycairo < 1.11:
    class Region:
        def __init__(self):
            return
        def union(self, rec):
            return
        def contains_rectangle(self, rec):
            # inhibit drawing
            return True
    class RectangleInt:
        def __init__(self, x, y, w, h):
            return
    REGION_OVERLAP_OUT = False

from cairo import Context, ImageSurface, FORMAT_A8, FORMAT_ARGB32, Matrix
from bitmap_to_surface import make_image_surface

def make_label(text, filename, size=12, angle=0):
    '''
    Parameters:
    -----------
    text : string
        Text to be displayed
    filename : string
        Path to a font
    size : int
        Font size in 1/64th points
    angle : float
        Text angle in degrees
    '''
    face = Face(filename)
    face.set_char_size( size*64 )
    # FT_Angle is a 16.16 fixed-point value expressed in degrees.
    angle = FT_Angle(angle * 65536)
    matrix  = FT_Matrix( FT_Cos( angle ),
                         - FT_Sin( angle ),
                         FT_Sin( angle ) ,
                         FT_Cos( angle ) )
    flags = FT_LOAD_RENDER
    pen = FT_Vector(0,0)
    FT_Set_Transform( face._FT_Face, byref(matrix), byref(pen) )
    previous = 0
    xmin, xmax = 0, 0
    ymin, ymax = 0, 0
    for c in text:
        face.load_char(c, flags)
        kerning = face.get_kerning(previous, c)
        previous = c
        bitmap = face.glyph.bitmap
        pitch  = face.glyph.bitmap.pitch
        width  = face.glyph.bitmap.width
        rows   = face.glyph.bitmap.rows
        top    = face.glyph.bitmap_top
        left   = face.glyph.bitmap_left
        pen.x += kerning.x
        x0 = (pen.x >> 6) + left
        x1 = x0 + width
        y0 = (pen.y >> 6) - (rows - top)
        y1 = y0 + rows
        xmin, xmax = min(xmin, x0),  max(xmax, x1)
        ymin, ymax = min(ymin, y0), max(ymax, y1)
        pen.x += face.glyph.advance.x
        pen.y += face.glyph.advance.y

    L = ImageSurface(FORMAT_A8, xmax-xmin, ymax-ymin)
    previous = 0
    pen.x, pen.y = 0, 0
    ctx = Context(L)
    for c in text:
        face.load_char(c, flags)
        kerning = face.get_kerning(previous, c)
        previous = c
        bitmap = face.glyph.bitmap
        pitch  = face.glyph.bitmap.pitch
        width  = face.glyph.bitmap.width
        rows   = face.glyph.bitmap.rows
        top    = face.glyph.bitmap_top
        left   = face.glyph.bitmap_left
        pen.x += kerning.x
        x = (pen.x >> 6) - xmin + left
        y = - (pen.y >> 6) + ymax - top
        if (width > 0):
            glyph_surface = make_image_surface(face.glyph.bitmap)
            ctx.set_source_surface(glyph_surface, x, y)
            ctx.paint()
        pen.x += face.glyph.advance.x
        pen.y += face.glyph.advance.y

    L.flush()
    return L


if __name__ == '__main__':
    from PIL import Image

    n_words = 200
    H, W, dpi = 600, 800, 72.0
    I = ImageSurface(FORMAT_A8, W, H)
    ctxI = Context(I)
    ctxI.rectangle(0,0,800,600)
    ctxI.set_source_rgba (0.9, 0.9, 0.9, 0)
    ctxI.fill()
    S = random.normal(0,1,n_words)
    S = (S-S.min())/(S.max()-S.min())
    S = sort(1-sqrt(S))[::-1]
    sizes = (12 + S*48).astype(int).tolist()

    def spiral():
        eccentricity = 1.5
        radius = 8
        step = 0.1
        t = 0
        while True:
            t += step
            yield eccentricity*radius*t*cos(t), radius*t*sin(t)

    drawn_regions = Region()
    for size in sizes:
        angle = random.randint(-25,25)
        try:
            L = make_label('Hello', './Vera.ttf', size, angle=angle)
        except NotImplementedError:
            raise SystemExit("For python 3.x, you need pycairo >= 1.11+ (from https://github.com/pygobject/pycairo)")
        h = L.get_height()
        w = L.get_width()
        if h < H and w < W:
            x0 = W//2 + (random.uniform()-.1)*50
            y0 = H//2 + (random.uniform()-.1)*50
            for dx,dy in spiral():
                c = .25+.75*random.random()
                x = int(x0+dx)
                y = int(y0+dy)
                checked = False
                I.flush()
                if not (x <= w//2 or y <= h//2 or x >= (W-w//2) or y >= (H-h//2)):
                    ndI = ndarray(shape=(h,w), buffer=I.get_data(), dtype=ubyte, order='C',
                                  offset=(x-w//2) + I.get_stride() * (y-h//2),
                                  strides=[I.get_stride(), 1])
                    ndL = ndarray(shape=(h,w), buffer=L.get_data(), dtype=ubyte, order='C',
                                  strides=[L.get_stride(), 1])
                    if ((ndI * ndL).sum() == 0):
                       checked = True
                new_region = RectangleInt(x-w//2, y-h//2, w, h)
                if  (checked or ( drawn_regions.contains_rectangle(new_region) == REGION_OVERLAP_OUT )):
                    ctxI.set_source_surface(L, 0, 0)
                    pattern = ctxI.get_source()
                    scalematrix = Matrix()
                    scalematrix.scale(1.0,1.0)
                    scalematrix.translate(w//2 - x, h//2 - y)
                    pattern.set_matrix(scalematrix)
                    ctxI.set_source_rgba(c,c,c,c)
                    ctxI.mask(pattern)
                    drawn_regions.union(new_region)
                    break
    I.flush()
    I.write_to_png("wordle-cairo.png")
    Image.open("wordle-cairo.png").show()