~launchpad-pqm/launchpad/devel

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
181
182
183
184
185
186
187
188
189
190
191
Sprites
=======

Many small images in Launchpad are combined into a single image file,
so that browsers do not have to make as many requests when they first
view a page. An individual sprite is displayed as a background image
on an element by setting the background position.

A new image can be added to the combined file with the command::

    make sprite_image

The resulting icon-sprites and icon-sprites.positioning files must
be committed to the repository. Any changes to the CSS template will
be automatically picked up when Launchpad is restarted. If you don't
want to restart launchpad.dev, you can run::

    make css_combine


CSS Template
------------

SpriteUtil takes a template css file that contains special comments
indicating that the background-image for a given rule should be
added to the combined image file and the rule should be updated.

For example::

    .add {
        background-image: url(/@@/edit.png); /* sprite-ref: group1 */
    }

would become
::

    .add {
        background-image: url(foo/combined_image.png);
        background-position: 0px 234px;
    }

The sprite-ref parameter specifies not only that the given image
should become a sprite but also that all the images with sprite-ref
value (in this case: "group1") will be combined to a single file.
A SpriteUtil object currently can only process a single group.

Loading the CSS Template
------------------------

Instatiating a new SpriteUtil object will parse the css template and
find all the css rules for the specified group. The url_prefix_substitutions
parameter allows you to convert url prefixes into file paths relative
to the directory passed into combineImages(). The space between sprites
in the file can be changed with the margin parameter.

    >>> import os, tempfile
    >>> import Image
    >>> from lp.services.spriteutils import SpriteUtil
    >>> root = os.path.abspath(os.path.join(__file__, '../../../../..'))
    >>> icing = os.path.join(root, 'lib/canonical/launchpad/icing')
    >>> new_png_file = tempfile.NamedTemporaryFile()
    >>> new_positioning_file = tempfile.NamedTemporaryFile()
    >>> new_css_file = tempfile.NamedTemporaryFile()
    >>> def get_sprite_util(margin=0):
    ...     return SpriteUtil(
    ...         os.path.join(
    ...             root, 'lib/lp/services/tests/testfiles/template.css'),
    ...         'group1',
    ...         url_prefix_substitutions={'/@@/': '../images/'},
    ...         margin=margin)
    >>> sprite_util = get_sprite_util()


Generate Image File
-------------------

The combined image will have a width equal to that of the widest image,
and the height will be the sum of all the image heights, since the margin
is currently zero.

    >>> sprite_util.combineImages(icing)
    >>> sprite_util.savePNG(new_png_file.name)
    >>> image = Image.open(new_png_file.name)
    >>> print image.size
    (32, 87)

The height will increase when the margin is increased.

    >>> sprite_util = get_sprite_util(margin=100)
    >>> sprite_util.combineImages(icing)
    >>> sprite_util.savePNG(new_png_file.name)
    >>> image = Image.open(new_png_file.name)
    >>> print image.size
    (32, 587)


Positioning File
----------------

The positioning file contains locations of each sprite in the combined
image file. This allows the css file to be regenerated when the template
changes without requiring the combined image file to be recreated.

    >>> sprite_util.savePositioning(new_positioning_file.name)
    >>> print new_positioning_file.read()
    /*...
    {
        "../images/edit.png": [
            0,
            -228
        ],
        "../images/flame-large.png": [
            0,
            -456
        ],
        "../images/blue-bar.png": [
            0,
            -342
        ],
        "../images/add.png": [
            0,
            -114
        ]
    }

The positions attribute can be cleared and loaded from the file.

    >>> print pretty(sprite_util.positions)
    {u'../images/add.png': (0, -114),
     u'../images/blue-bar.png': (0, -342),
     u'../images/edit.png': (0, -228),
     u'../images/flame-large.png': (0, -456)}
    >>> sprite_util.positions = None
    >>> sprite_util.loadPositioning(new_positioning_file.name)
    >>> print pretty(sprite_util.positions)
    {'../images/add.png': [0, -114],
     '../images/blue-bar.png': [0, -342],
     '../images/edit.png': [0, -228],
     '../images/flame-large.png': [0, -456]}


Generate CSS File
-----------------

When the css file is generated, the second parameter is the relative
path from the css file to the combined image file. The .add and .foo
classes have the same background-position, since they both originally
referenced /@@/add.png, which was only added once to the combined file.
.bar and .info do not have a background-position and the background-image
is not group1.png, since its sprite-ref is "group2".

    >>> sprite_util.saveConvertedCSS(new_css_file.name, 'group1.png')
    >>> print new_css_file.read()
    /*...
    .add {
        background-image: url(group1.png);
        /* sprite-ref: group1 */
        background-position: 0 -114px
        }
    .foo {
        background-image: url(group1.png);
        /* sprite-ref: group1 */
        background-position: 0 -114px
        }
    .bar {
        background-image: url(/@@/add.png);
        /* sprite-ref: group2 */
        }
    .edit {
        background-image: url(group1.png);
        /* sprite-ref: group1 */
        background-repeat: no-repeat;
        background-position: 0 -228px
        }
    .info {
        background-image: url(/@@/info.png);
        /* sprite-ref: group2 */
        background-repeat: no-repeat
        }
    .bluebar {
        background-image: url(group1.png);
        /* sprite-ref: group1 */
        background-repeat: repeat-x;
        background-position: 0 -342px
        }
    .large-flame {
        background-image: url(group1.png);
        /* sprite-ref: group1 */
        background-repeat: no-repeat;
        background-position: 0 -456px
        }