layout/reftests/border-image/gen-refs.py

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

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

mercurial