|
1 # This Source Code Form is subject to the terms of the Mozilla Public |
|
2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 |
|
5 # Generates tables of background images which correspond with border images for |
|
6 # creating reftests. Input is the filename containing input defined below (a subset |
|
7 # of the allowed CSS border properties). An html representation of a table is |
|
8 # output to stdout. |
|
9 # |
|
10 # Usage: python gen-refs.py input_filename |
|
11 # |
|
12 # Input must take the form (order is not important, nothing is optional, distance in order top, right, bottom, left): |
|
13 # width: p; |
|
14 # height: p; |
|
15 # border-width: p; |
|
16 # border-image-source: ...; |
|
17 # border-image-slice: p p p p; |
|
18 # note that actually border-image-slice takes numbers without px, which represent pixels anyway (or at least coords) |
|
19 # border-image-width: np np np np; |
|
20 # border-image-repeat: stretch | repeat | round; |
|
21 # border-image-outset: np np np np; |
|
22 # |
|
23 # where: |
|
24 # p ::= n'px' |
|
25 # np ::= n | p |
|
26 # |
|
27 # Assumes there is no intrinsic size for the border-image-source, so uses |
|
28 # the size of the border image area. |
|
29 |
|
30 import sys |
|
31 |
|
32 class Point: |
|
33 def __init__(self, w=0, h=0): |
|
34 self.x = w |
|
35 self.y = h |
|
36 class Size: |
|
37 def __init__(self, w=0, h=0): |
|
38 self.width = w |
|
39 self.height = h |
|
40 class Rect: |
|
41 def __init__(self, x=0, y=0, x2=0, y2=0): |
|
42 self.x = x |
|
43 self.y = y |
|
44 self.x2 = x2 |
|
45 self.y2 = y2 |
|
46 def width(self): |
|
47 return self.x2 - self.x |
|
48 def height(self): |
|
49 return self.y2 - self.y |
|
50 |
|
51 class Props: |
|
52 def __init__(self): |
|
53 self.size = Size() |
|
54 |
|
55 class np: |
|
56 def __init__(self, n, p): |
|
57 self.n = n |
|
58 self.p = p |
|
59 |
|
60 def get_absolute(self, ref): |
|
61 if not self.p == 0: |
|
62 return self.p |
|
63 return self.n * ref |
|
64 |
|
65 def parse_p(tok): |
|
66 if tok[-2:] == "px": |
|
67 return float(tok[:-2]) |
|
68 print "Whoops, not a pixel value " + tok |
|
69 |
|
70 def parse_np(tok): |
|
71 if tok[-2:] == "px": |
|
72 return np(0, float(tok[:-2])) |
|
73 return np(float(tok), 0) |
|
74 |
|
75 def parse(filename): |
|
76 f = open(filename, "r") |
|
77 props = Props() |
|
78 for l in f: |
|
79 l = l.strip() |
|
80 if not l[-1] == ";": |
|
81 continue |
|
82 toks = l[:-1].split() |
|
83 if toks[0] == "border-width:": |
|
84 props.width = parse_p(toks[1]) |
|
85 if toks[0] == "height:": |
|
86 props.size.height = parse_p(toks[1]) |
|
87 if toks[0] == "width:": |
|
88 props.size.width = parse_p(toks[1]) |
|
89 if toks[0] == "border-image-source:": |
|
90 props.source = l[l.find(":")+1:l.rfind(";")].strip() |
|
91 if toks[0] == "border-image-repeat:": |
|
92 props.repeat = toks[1] |
|
93 if toks[0] == "border-image-slice:": |
|
94 props.slice = map(parse_p, toks[1:5]) |
|
95 if toks[0] == "border-image-width:": |
|
96 props.image_width = map(parse_np, toks[1:5]) |
|
97 if toks[0] == "border-image-outset:": |
|
98 props.outset = map(parse_np, toks[1:5]) |
|
99 f.close() |
|
100 return props |
|
101 |
|
102 # the result of normalisation is that all sizes are in pixels and the size, |
|
103 # widths, and outset have been normalised to a size and width - the former is |
|
104 # the element's interior, the latter is the width of the drawn border. |
|
105 def normalise(props): |
|
106 result = Props() |
|
107 result.source = props.source |
|
108 result.repeat = props.repeat |
|
109 result.width = map(lambda x: x.get_absolute(props.width), props.image_width) |
|
110 outsets = map(lambda x: x.get_absolute(props.width), props.outset) |
|
111 result.size.width = props.size.width + 2*props.width + outsets[1] + outsets[3] |
|
112 result.size.height = props.size.height + 2*props.width + outsets[0] + outsets[2] |
|
113 result.slice = props.slice |
|
114 for i in [0,2]: |
|
115 if result.slice[i] > result.size.height: |
|
116 result.slice[i] = result.size.height |
|
117 if result.slice[i+1] > result.size.width: |
|
118 result.slice[i+1] = result.size.width |
|
119 |
|
120 return result |
|
121 |
|
122 def check_parse(props): |
|
123 if not hasattr(props, 'source'): |
|
124 print "missing border-image-source" |
|
125 return False |
|
126 if not hasattr(props.size, 'width'): |
|
127 print "missing width" |
|
128 return False |
|
129 if not hasattr(props.size, 'height'): |
|
130 print "missing height" |
|
131 return False |
|
132 if not hasattr(props, 'width'): |
|
133 print "missing border-width" |
|
134 return False |
|
135 if not hasattr(props, 'image_width'): |
|
136 print "missing border-image-width" |
|
137 return False |
|
138 if not hasattr(props, 'slice'): |
|
139 print "missing border-image-slice" |
|
140 return False |
|
141 if not hasattr(props, 'repeat') or (props.repeat not in ["stretch", "repeat", "round"]): |
|
142 print "missing or incorrect border-image-repeat '" + props.repeat + "'" |
|
143 return False |
|
144 if not hasattr(props, 'outset'): |
|
145 print "missing border-image-outset" |
|
146 return False |
|
147 |
|
148 return True |
|
149 |
|
150 def check_normalise(props): |
|
151 if not hasattr(props, 'source'): |
|
152 print "missing border-image-source" |
|
153 return False |
|
154 if not hasattr(props.size, 'width'): |
|
155 print "missing width" |
|
156 return False |
|
157 if not hasattr(props.size, 'height'): |
|
158 print "missing height" |
|
159 return False |
|
160 if not hasattr(props, 'slice'): |
|
161 print "missing border-image-slice" |
|
162 return False |
|
163 if not hasattr(props, 'repeat') or (props.repeat not in ["stretch", "repeat", "round"]): |
|
164 print "missing or incorrect border-image-repeat '" + props.repeat + "'" |
|
165 return False |
|
166 |
|
167 return True |
|
168 |
|
169 class Tile: |
|
170 def __init__(self): |
|
171 self.slice = Rect() |
|
172 self.border_width = Rect() |
|
173 |
|
174 # throughout, we will use arrays for nine-patches, the indices correspond thusly: |
|
175 # 0 1 2 |
|
176 # 3 4 5 |
|
177 # 6 7 8 |
|
178 |
|
179 # Compute the source tiles' slice and border-width sizes |
|
180 def make_src_tiles(): |
|
181 tiles = [Tile() for i in range(9)] |
|
182 |
|
183 rows = [range(3*i, 3*(i+1)) for i in range(3)] |
|
184 cols = [[i, i+3, i+6] for i in range(3)] |
|
185 |
|
186 row_limits_slice = [0, props.slice[3], props.size.width - props.slice[1], props.size.width] |
|
187 row_limits_width = [0, props.width[3], props.size.width - props.width[1], props.size.width] |
|
188 for r in range(3): |
|
189 for t in [tiles[i] for i in cols[r]]: |
|
190 t.slice.x = row_limits_slice[r] |
|
191 t.slice.x2 = row_limits_slice[r+1] |
|
192 t.border_width.x = row_limits_width[r] |
|
193 t.border_width.x2 = row_limits_width[r+1] |
|
194 |
|
195 col_limits_slice = [0, props.slice[0], props.size.height - props.slice[2], props.size.height] |
|
196 col_limits_width = [0, props.width[0], props.size.height - props.width[2], props.size.height] |
|
197 for c in range(3): |
|
198 for t in [tiles[i] for i in rows[c]]: |
|
199 t.slice.y = col_limits_slice[c] |
|
200 t.slice.y2 = col_limits_slice[c+1] |
|
201 t.border_width.y = col_limits_width[c] |
|
202 t.border_width.y2 = col_limits_width[c+1] |
|
203 |
|
204 return tiles |
|
205 |
|
206 def compute(props): |
|
207 tiles = make_src_tiles() |
|
208 |
|
209 # corners scale easy |
|
210 for t in [tiles[i] for i in [0, 2, 6, 8]]: |
|
211 t.scale = Point(t.border_width.width()/t.slice.width(), t.border_width.height()/t.slice.height()) |
|
212 # edges are by their secondary dimension |
|
213 for t in [tiles[i] for i in [1, 7]]: |
|
214 t.scale = Point(t.border_width.height()/t.slice.height(), t.border_width.height()/t.slice.height()) |
|
215 for t in [tiles[i] for i in [3, 5]]: |
|
216 t.scale = Point(t.border_width.width()/t.slice.width(), t.border_width.width()/t.slice.width()) |
|
217 # the middle is scaled by the factors for the top and left edges |
|
218 tiles[4].scale = Point(tiles[1].scale.x, tiles[3].scale.y) |
|
219 |
|
220 # the size of a source tile for the middle section |
|
221 src_tile_size = Size(tiles[4].slice.width()*tiles[4].scale.x, tiles[4].slice.height()*tiles[4].scale.y) |
|
222 |
|
223 # the size of a single destination tile in the central part |
|
224 dest_tile_size = Size() |
|
225 if props.repeat == "stretch": |
|
226 dest_tile_size.width = tiles[4].border_width.width() |
|
227 dest_tile_size.height = tiles[4].border_width.height() |
|
228 for t in [tiles[i] for i in [1, 7]]: |
|
229 t.scale.x = t.border_width.width()/t.slice.width() |
|
230 for t in [tiles[i] for i in [3, 5]]: |
|
231 t.scale.y = t.border_width.height()/t.slice.height() |
|
232 elif props.repeat == "repeat": |
|
233 dest_tile_size = src_tile_size |
|
234 elif props.repeat == "round": |
|
235 dest_tile_size.width = tiles[4].border_width.width() / math.ceil(tiles[4].border_width.width() / src_tile_size.width) |
|
236 dest_tile_size.height = tiles[4].border_width.height() / math.ceil(tiles[4].border_width.height() / src_tile_size.height) |
|
237 for t in [tiles[i] for i in [1, 4, 7]]: |
|
238 t.scale.x = dest_tile_size.width/t.slice.width() |
|
239 for t in [tiles[i] for i in [3, 4, 5]]: |
|
240 t.scale.y = dest_tile_size.height/t.slice.height() |
|
241 else: |
|
242 print "Whoops, invalid border-image-repeat value" |
|
243 |
|
244 # catch overlapping slices. Its easier to deal with it here than to catch |
|
245 # earlier and have to avoid all the divide by zeroes above |
|
246 for t in tiles: |
|
247 if t.slice.width() < 0: |
|
248 t.scale.x = 0 |
|
249 if t.slice.height() < 0: |
|
250 t.scale.y = 0 |
|
251 |
|
252 tiles_h = int(math.ceil(tiles[4].border_width.width()/dest_tile_size.width)+2) |
|
253 tiles_v = int(math.ceil(tiles[4].border_width.height()/dest_tile_size.height)+2) |
|
254 |
|
255 # if border-image-repeat: repeat, then we will later center the tiles, that |
|
256 # means we need an extra tile for the two 'half' tiles at either end |
|
257 if props.repeat == "repeat": |
|
258 if tiles_h % 2 == 0: |
|
259 tiles_h += 1 |
|
260 if tiles_v % 2 == 0: |
|
261 tiles_v += 1 |
|
262 dest_tiles = [Tile() for i in range(tiles_h * tiles_v)] |
|
263 |
|
264 # corners |
|
265 corners = [(0, 0), (tiles_h-1, 2), (tiles_v*(tiles_h-1), 6), (tiles_v*tiles_h-1, 8)] |
|
266 for d,s in corners: |
|
267 dest_tiles[d].size = Size(tiles[s].scale.x*props.size.width, tiles[s].scale.y*props.size.height) |
|
268 dest_tiles[d].dest_size = Size(tiles[s].border_width.width(), tiles[s].border_width.height()) |
|
269 dest_tiles[0].offset = Point(0, 0) |
|
270 dest_tiles[tiles_h-1].offset = Point(tiles[2].border_width.width() - dest_tiles[tiles_h-1].size.width, 0) |
|
271 dest_tiles[tiles_v*(tiles_h-1)].offset = Point(0, tiles[6].border_width.height() - dest_tiles[tiles_v*(tiles_h-1)].size.height) |
|
272 dest_tiles[tiles_v*tiles_h-1].offset = Point(tiles[8].border_width.width() - dest_tiles[tiles_h*tiles_v-1].size.width, tiles[8].border_width.height() - dest_tiles[tiles_h*tiles_v-1].size.height) |
|
273 |
|
274 # horizontal edges |
|
275 for i in range(1, tiles_h-1): |
|
276 dest_tiles[i].size = Size(tiles[1].scale.x*props.size.width, tiles[1].scale.y*props.size.height) |
|
277 dest_tiles[(tiles_v-1)*tiles_h + i].size = Size(tiles[7].scale.x*props.size.width, tiles[7].scale.y*props.size.height) |
|
278 dest_tiles[i].dest_size = Size(dest_tile_size.width, tiles[1].border_width.height()) |
|
279 dest_tiles[(tiles_v-1)*tiles_h + i].dest_size = Size(dest_tile_size.width, tiles[7].border_width.height()) |
|
280 dest_tiles[i].offset = Point(-tiles[1].scale.x*tiles[1].slice.x, -tiles[1].scale.y*tiles[1].slice.y) |
|
281 dest_tiles[(tiles_v-1)*tiles_h + i].offset = Point(-tiles[7].scale.x*tiles[7].slice.x, -tiles[7].scale.y*tiles[7].slice.y) |
|
282 |
|
283 # vertical edges |
|
284 for i in range(1, tiles_v-1): |
|
285 dest_tiles[i*tiles_h].size = Size(tiles[3].scale.x*props.size.width, tiles[3].scale.y*props.size.height) |
|
286 dest_tiles[(i+1)*tiles_h-1].size = Size(tiles[5].scale.x*props.size.width, tiles[5].scale.y*props.size.height) |
|
287 dest_tiles[i*tiles_h].dest_size = Size(tiles[3].border_width.width(), dest_tile_size.height) |
|
288 dest_tiles[(i+1)*tiles_h-1].dest_size = Size(tiles[5].border_width.width(), dest_tile_size.height) |
|
289 dest_tiles[i*tiles_h].offset = Point(-tiles[3].scale.x*tiles[3].slice.x, -tiles[3].scale.y*tiles[3].slice.y) |
|
290 dest_tiles[(i+1)*tiles_h-1].offset = Point(-tiles[5].scale.x*tiles[5].slice.x, -tiles[5].scale.y*tiles[5].slice.y) |
|
291 |
|
292 # middle |
|
293 for i in range(1, tiles_v-1): |
|
294 for j in range(1, tiles_h-1): |
|
295 dest_tiles[i*tiles_h+j].size = Size(tiles[4].scale.x*props.size.width, tiles[4].scale.y*props.size.height) |
|
296 dest_tiles[i*tiles_h+j].offset = Point(-tiles[4].scale.x*tiles[4].slice.x, -tiles[4].scale.y*tiles[4].slice.y) |
|
297 dest_tiles[i*tiles_h+j].dest_size = dest_tile_size |
|
298 |
|
299 # edge and middle tiles are centered with border-image-repeat: repeat |
|
300 # we need to change the offset to take account of this and change the dest_size |
|
301 # of the tiles at the sides of the edges if they are clipped |
|
302 if props.repeat == "repeat": |
|
303 diff_h = ((tiles_h-2)*dest_tile_size.width - tiles[4].border_width.width()) / 2 |
|
304 diff_v = ((tiles_v-2)*dest_tile_size.height - tiles[4].border_width.height()) / 2 |
|
305 for i in range(0, tiles_h): |
|
306 dest_tiles[tiles_h + i].dest_size.height -= diff_v |
|
307 dest_tiles[tiles_h + i].offset.y -= diff_v #* tiles[4].scale.y |
|
308 dest_tiles[(tiles_v-2)*tiles_h + i].dest_size.height -= diff_v |
|
309 for i in range(0, tiles_v): |
|
310 dest_tiles[i*tiles_h + 1].dest_size.width -= diff_h |
|
311 dest_tiles[i*tiles_h + 1].offset.x -= diff_h #* tiles[4].scale.x |
|
312 dest_tiles[(i+1)*tiles_h-2].dest_size.width -= diff_h |
|
313 |
|
314 # output the table to simulate the border |
|
315 print "<table>" |
|
316 for i in range(tiles_h): |
|
317 print "<col style=\"width: " + str(dest_tiles[i].dest_size.width) + "px;\">" |
|
318 for i in range(tiles_v): |
|
319 print "<tr style=\"height: " + str(dest_tiles[i*tiles_h].dest_size.height) + "px;\">" |
|
320 for j in range(tiles_h): |
|
321 width = dest_tiles[i*tiles_h+j].size.width |
|
322 height = dest_tiles[i*tiles_h+j].size.height |
|
323 # catch any tiles with negative widths/heights |
|
324 # this happends when the total of the border-image-slices > borde drawing area |
|
325 if width <= 0 or height <= 0: |
|
326 print " <td style=\"background: white;\"></td>" |
|
327 else: |
|
328 print " <td style=\"background-image: " + props.source + "; background-size: " + str(width) + "px " + str(height) + "px; background-position: " + str(dest_tiles[i*tiles_h+j].offset.x) + "px " + str(dest_tiles[i*tiles_h+j].offset.y) + "px;\"></td>" |
|
329 print "</tr>" |
|
330 print "</table>" |
|
331 |
|
332 |
|
333 # start here |
|
334 args = sys.argv[1:] |
|
335 if len(args) == 0: |
|
336 print "whoops: no source file" |
|
337 exit(1) |
|
338 |
|
339 |
|
340 props = parse(args[0]) |
|
341 if not check_parse(props): |
|
342 print dir(props) |
|
343 exit(1) |
|
344 props = normalise(props) |
|
345 if not check_normalise(props): |
|
346 exit(1) |
|
347 compute(props) |