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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/layout/reftests/border-image/gen-refs.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,347 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +# Generates tables of background images which correspond with border images for
     1.9 +# creating reftests. Input is the filename containing input defined below (a subset
    1.10 +# of the allowed CSS border properties). An html representation of a table is
    1.11 +# output to stdout.
    1.12 +#
    1.13 +# Usage: python gen-refs.py input_filename
    1.14 +#
    1.15 +# Input must take the form (order is not important, nothing is optional, distance in order top, right, bottom, left):
    1.16 +# width: p;
    1.17 +# height: p;
    1.18 +# border-width: p;
    1.19 +# border-image-source: ...;
    1.20 +# border-image-slice: p p p p;
    1.21 +# note that actually border-image-slice takes numbers without px, which represent pixels anyway (or at least coords)
    1.22 +# border-image-width: np np np np;
    1.23 +# border-image-repeat: stretch | repeat | round;
    1.24 +# border-image-outset: np np np np;
    1.25 +#
    1.26 +# where:
    1.27 +# p ::= n'px'
    1.28 +# np ::= n | p
    1.29 +#
    1.30 +# Assumes there is no intrinsic size for the border-image-source, so uses
    1.31 +# the size of the border image area.
    1.32 +
    1.33 +import sys
    1.34 +
    1.35 +class Point:
    1.36 +  def __init__(self, w=0, h=0):
    1.37 +    self.x = w
    1.38 +    self.y = h
    1.39 +class Size:
    1.40 +  def __init__(self, w=0, h=0):
    1.41 +    self.width = w
    1.42 +    self.height = h
    1.43 +class Rect:
    1.44 +  def __init__(self, x=0, y=0, x2=0, y2=0):
    1.45 +    self.x = x
    1.46 +    self.y = y
    1.47 +    self.x2 = x2
    1.48 +    self.y2 = y2
    1.49 +  def width(self):
    1.50 +    return self.x2 - self.x
    1.51 +  def height(self):
    1.52 +    return self.y2 - self.y
    1.53 +
    1.54 +class Props:
    1.55 +  def __init__(self):
    1.56 +    self.size = Size()
    1.57 +
    1.58 +class np:
    1.59 +  def __init__(self, n, p):
    1.60 +    self.n = n
    1.61 +    self.p = p
    1.62 +
    1.63 +  def get_absolute(self, ref):
    1.64 +    if not self.p == 0:
    1.65 +      return self.p
    1.66 +    return self.n * ref
    1.67 +
    1.68 +def parse_p(tok):
    1.69 +  if tok[-2:] == "px":
    1.70 +    return float(tok[:-2])
    1.71 +  print "Whoops, not a pixel value " + tok
    1.72 +
    1.73 +def parse_np(tok):
    1.74 +  if tok[-2:] == "px":
    1.75 +    return np(0, float(tok[:-2]))
    1.76 +  return np(float(tok), 0)
    1.77 +
    1.78 +def parse(filename):
    1.79 +  f = open(filename, "r")
    1.80 +  props = Props()
    1.81 +  for l in f:
    1.82 +    l = l.strip()
    1.83 +    if not l[-1] == ";":
    1.84 +      continue
    1.85 +    toks = l[:-1].split()
    1.86 +    if toks[0] == "border-width:":
    1.87 +      props.width = parse_p(toks[1])
    1.88 +    if toks[0] == "height:":
    1.89 +      props.size.height = parse_p(toks[1])
    1.90 +    if toks[0] == "width:":
    1.91 +      props.size.width = parse_p(toks[1])
    1.92 +    if toks[0] == "border-image-source:":
    1.93 +      props.source = l[l.find(":")+1:l.rfind(";")].strip()
    1.94 +    if toks[0] == "border-image-repeat:":
    1.95 +      props.repeat = toks[1]
    1.96 +    if toks[0] == "border-image-slice:":
    1.97 +      props.slice = map(parse_p, toks[1:5])
    1.98 +    if toks[0] == "border-image-width:":
    1.99 +      props.image_width = map(parse_np, toks[1:5])
   1.100 +    if toks[0] == "border-image-outset:":
   1.101 +      props.outset = map(parse_np, toks[1:5])
   1.102 +  f.close()
   1.103 +  return props
   1.104 +
   1.105 +# the result of normalisation is that all sizes are in pixels and the size,
   1.106 +# widths, and outset have been normalised to a size and width - the former is
   1.107 +# the element's interior, the latter is the width of the drawn border.
   1.108 +def normalise(props):
   1.109 +  result = Props()
   1.110 +  result.source = props.source
   1.111 +  result.repeat = props.repeat
   1.112 +  result.width = map(lambda x: x.get_absolute(props.width), props.image_width)
   1.113 +  outsets = map(lambda x: x.get_absolute(props.width), props.outset)
   1.114 +  result.size.width = props.size.width + 2*props.width + outsets[1] + outsets[3]
   1.115 +  result.size.height = props.size.height + 2*props.width + outsets[0] + outsets[2]
   1.116 +  result.slice = props.slice
   1.117 +  for i in [0,2]:
   1.118 +    if result.slice[i] > result.size.height:
   1.119 +      result.slice[i] = result.size.height
   1.120 +    if result.slice[i+1] > result.size.width:
   1.121 +      result.slice[i+1] = result.size.width
   1.122 +
   1.123 +  return result
   1.124 +
   1.125 +def check_parse(props):
   1.126 +  if not hasattr(props, 'source'):
   1.127 +    print "missing border-image-source"
   1.128 +    return False
   1.129 +  if not hasattr(props.size, 'width'):
   1.130 +    print "missing width"
   1.131 +    return False
   1.132 +  if not hasattr(props.size, 'height'):
   1.133 +    print "missing height"
   1.134 +    return False
   1.135 +  if not hasattr(props, 'width'):
   1.136 +    print "missing border-width"
   1.137 +    return False
   1.138 +  if not hasattr(props, 'image_width'):
   1.139 +    print "missing border-image-width"
   1.140 +    return False
   1.141 +  if not hasattr(props, 'slice'):
   1.142 +    print "missing border-image-slice"
   1.143 +    return False
   1.144 +  if not hasattr(props, 'repeat') or (props.repeat not in ["stretch", "repeat", "round"]):
   1.145 +    print "missing or incorrect border-image-repeat '" + props.repeat + "'"
   1.146 +    return False
   1.147 +  if not hasattr(props, 'outset'):
   1.148 +    print "missing border-image-outset"
   1.149 +    return False
   1.150 +
   1.151 +  return True
   1.152 +
   1.153 +def check_normalise(props):
   1.154 +  if not hasattr(props, 'source'):
   1.155 +    print "missing border-image-source"
   1.156 +    return False
   1.157 +  if not hasattr(props.size, 'width'):
   1.158 +    print "missing width"
   1.159 +    return False
   1.160 +  if not hasattr(props.size, 'height'):
   1.161 +    print "missing height"
   1.162 +    return False
   1.163 +  if not hasattr(props, 'slice'):
   1.164 +    print "missing border-image-slice"
   1.165 +    return False
   1.166 +  if not hasattr(props, 'repeat') or (props.repeat not in ["stretch", "repeat", "round"]):
   1.167 +    print "missing or incorrect border-image-repeat '" + props.repeat + "'"
   1.168 +    return False
   1.169 +
   1.170 +  return True
   1.171 +
   1.172 +class Tile:
   1.173 +  def __init__(self):
   1.174 +    self.slice = Rect()
   1.175 +    self.border_width = Rect()
   1.176 +
   1.177 +# throughout, we will use arrays for nine-patches, the indices correspond thusly:
   1.178 +# 0 1 2
   1.179 +# 3 4 5
   1.180 +# 6 7 8
   1.181 +
   1.182 +# Compute the source tiles' slice and border-width sizes
   1.183 +def make_src_tiles():
   1.184 +  tiles = [Tile() for i in range(9)]
   1.185 +
   1.186 +  rows = [range(3*i, 3*(i+1)) for i in range(3)]
   1.187 +  cols = [[i, i+3, i+6] for i in range(3)]
   1.188 +
   1.189 +  row_limits_slice = [0, props.slice[3], props.size.width - props.slice[1], props.size.width]
   1.190 +  row_limits_width = [0, props.width[3], props.size.width - props.width[1], props.size.width]
   1.191 +  for r in range(3):
   1.192 +    for t in [tiles[i] for i in cols[r]]:
   1.193 +      t.slice.x = row_limits_slice[r]
   1.194 +      t.slice.x2 = row_limits_slice[r+1]
   1.195 +      t.border_width.x = row_limits_width[r]
   1.196 +      t.border_width.x2 = row_limits_width[r+1]
   1.197 +
   1.198 +  col_limits_slice = [0, props.slice[0], props.size.height - props.slice[2], props.size.height]
   1.199 +  col_limits_width = [0, props.width[0], props.size.height - props.width[2], props.size.height]
   1.200 +  for c in range(3):
   1.201 +    for t in [tiles[i] for i in rows[c]]:
   1.202 +      t.slice.y = col_limits_slice[c]
   1.203 +      t.slice.y2 = col_limits_slice[c+1]
   1.204 +      t.border_width.y = col_limits_width[c]
   1.205 +      t.border_width.y2 = col_limits_width[c+1]
   1.206 +
   1.207 +  return tiles
   1.208 +
   1.209 +def compute(props):
   1.210 +  tiles = make_src_tiles()
   1.211 +
   1.212 +  # corners scale easy
   1.213 +  for t in [tiles[i] for i in [0, 2, 6, 8]]:
   1.214 +    t.scale = Point(t.border_width.width()/t.slice.width(), t.border_width.height()/t.slice.height())
   1.215 +  # edges are by their secondary dimension
   1.216 +  for t in [tiles[i] for i in [1, 7]]:
   1.217 +    t.scale = Point(t.border_width.height()/t.slice.height(), t.border_width.height()/t.slice.height())
   1.218 +  for t in [tiles[i] for i in [3, 5]]:
   1.219 +    t.scale = Point(t.border_width.width()/t.slice.width(), t.border_width.width()/t.slice.width())
   1.220 +  # the middle is scaled by the factors for the top and left edges
   1.221 +  tiles[4].scale = Point(tiles[1].scale.x, tiles[3].scale.y)
   1.222 +
   1.223 +  # the size of a source tile for the middle section
   1.224 +  src_tile_size = Size(tiles[4].slice.width()*tiles[4].scale.x, tiles[4].slice.height()*tiles[4].scale.y)
   1.225 +
   1.226 +  # the size of a single destination tile in the central part
   1.227 +  dest_tile_size = Size()
   1.228 +  if props.repeat == "stretch":
   1.229 +    dest_tile_size.width = tiles[4].border_width.width()
   1.230 +    dest_tile_size.height = tiles[4].border_width.height()
   1.231 +    for t in [tiles[i] for i in [1, 7]]:
   1.232 +      t.scale.x = t.border_width.width()/t.slice.width()
   1.233 +    for t in [tiles[i] for i in [3, 5]]:
   1.234 +      t.scale.y = t.border_width.height()/t.slice.height()
   1.235 +  elif props.repeat == "repeat":
   1.236 +    dest_tile_size = src_tile_size
   1.237 +  elif props.repeat == "round":
   1.238 +    dest_tile_size.width = tiles[4].border_width.width() / math.ceil(tiles[4].border_width.width() / src_tile_size.width)
   1.239 +    dest_tile_size.height = tiles[4].border_width.height() / math.ceil(tiles[4].border_width.height() / src_tile_size.height)
   1.240 +    for t in [tiles[i] for i in [1, 4, 7]]:
   1.241 +      t.scale.x = dest_tile_size.width/t.slice.width()
   1.242 +    for t in [tiles[i] for i in [3, 4, 5]]:
   1.243 +      t.scale.y = dest_tile_size.height/t.slice.height()
   1.244 +  else:
   1.245 +    print "Whoops, invalid border-image-repeat value"
   1.246 +
   1.247 +  # catch overlapping slices. Its easier to deal with it here than to catch
   1.248 +  # earlier and have to avoid all the divide by zeroes above
   1.249 +  for t in tiles:
   1.250 +    if t.slice.width() < 0:
   1.251 +      t.scale.x = 0
   1.252 +    if t.slice.height() < 0:
   1.253 +      t.scale.y = 0
   1.254 +
   1.255 +  tiles_h = int(math.ceil(tiles[4].border_width.width()/dest_tile_size.width)+2)
   1.256 +  tiles_v = int(math.ceil(tiles[4].border_width.height()/dest_tile_size.height)+2)
   1.257 +
   1.258 +  # if border-image-repeat: repeat, then we will later center the tiles, that
   1.259 +  # means we need an extra tile for the two 'half' tiles at either end
   1.260 +  if props.repeat == "repeat":
   1.261 +    if tiles_h % 2 == 0:
   1.262 +      tiles_h += 1
   1.263 +    if tiles_v % 2 == 0:
   1.264 +      tiles_v += 1
   1.265 +  dest_tiles = [Tile() for i in range(tiles_h * tiles_v)]
   1.266 +
   1.267 +  # corners
   1.268 +  corners = [(0, 0), (tiles_h-1, 2), (tiles_v*(tiles_h-1), 6), (tiles_v*tiles_h-1, 8)]
   1.269 +  for d,s in corners:
   1.270 +    dest_tiles[d].size = Size(tiles[s].scale.x*props.size.width, tiles[s].scale.y*props.size.height)
   1.271 +    dest_tiles[d].dest_size = Size(tiles[s].border_width.width(), tiles[s].border_width.height())
   1.272 +  dest_tiles[0].offset                   = Point(0, 0)
   1.273 +  dest_tiles[tiles_h-1].offset           = Point(tiles[2].border_width.width() - dest_tiles[tiles_h-1].size.width, 0)
   1.274 +  dest_tiles[tiles_v*(tiles_h-1)].offset = Point(0, tiles[6].border_width.height() - dest_tiles[tiles_v*(tiles_h-1)].size.height)
   1.275 +  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)
   1.276 +
   1.277 +  # horizontal edges
   1.278 +  for i in range(1, tiles_h-1):
   1.279 +    dest_tiles[i].size                       = Size(tiles[1].scale.x*props.size.width, tiles[1].scale.y*props.size.height)
   1.280 +    dest_tiles[(tiles_v-1)*tiles_h + i].size = Size(tiles[7].scale.x*props.size.width, tiles[7].scale.y*props.size.height)
   1.281 +    dest_tiles[i].dest_size                       = Size(dest_tile_size.width, tiles[1].border_width.height())
   1.282 +    dest_tiles[(tiles_v-1)*tiles_h + i].dest_size = Size(dest_tile_size.width, tiles[7].border_width.height())
   1.283 +    dest_tiles[i].offset                       = Point(-tiles[1].scale.x*tiles[1].slice.x, -tiles[1].scale.y*tiles[1].slice.y)
   1.284 +    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)
   1.285 +
   1.286 +  # vertical edges
   1.287 +  for i in range(1, tiles_v-1):
   1.288 +    dest_tiles[i*tiles_h].size       = Size(tiles[3].scale.x*props.size.width, tiles[3].scale.y*props.size.height)
   1.289 +    dest_tiles[(i+1)*tiles_h-1].size = Size(tiles[5].scale.x*props.size.width, tiles[5].scale.y*props.size.height)
   1.290 +    dest_tiles[i*tiles_h].dest_size       = Size(tiles[3].border_width.width(), dest_tile_size.height)
   1.291 +    dest_tiles[(i+1)*tiles_h-1].dest_size = Size(tiles[5].border_width.width(), dest_tile_size.height)
   1.292 +    dest_tiles[i*tiles_h].offset       = Point(-tiles[3].scale.x*tiles[3].slice.x, -tiles[3].scale.y*tiles[3].slice.y)
   1.293 +    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)
   1.294 +
   1.295 +  # middle
   1.296 +  for i in range(1, tiles_v-1):
   1.297 +    for j in range(1, tiles_h-1):
   1.298 +      dest_tiles[i*tiles_h+j].size = Size(tiles[4].scale.x*props.size.width, tiles[4].scale.y*props.size.height)
   1.299 +      dest_tiles[i*tiles_h+j].offset = Point(-tiles[4].scale.x*tiles[4].slice.x, -tiles[4].scale.y*tiles[4].slice.y)
   1.300 +      dest_tiles[i*tiles_h+j].dest_size = dest_tile_size
   1.301 +
   1.302 +  # edge and middle tiles are centered with border-image-repeat: repeat
   1.303 +  # we need to change the offset to take account of this and change the dest_size
   1.304 +  # of the tiles at the sides of the edges if they are clipped
   1.305 +  if props.repeat == "repeat":
   1.306 +    diff_h = ((tiles_h-2)*dest_tile_size.width - tiles[4].border_width.width()) / 2
   1.307 +    diff_v = ((tiles_v-2)*dest_tile_size.height - tiles[4].border_width.height()) / 2
   1.308 +    for i in range(0, tiles_h):
   1.309 +      dest_tiles[tiles_h + i].dest_size.height -= diff_v
   1.310 +      dest_tiles[tiles_h + i].offset.y -= diff_v #* tiles[4].scale.y
   1.311 +      dest_tiles[(tiles_v-2)*tiles_h + i].dest_size.height -= diff_v
   1.312 +    for i in range(0, tiles_v):
   1.313 +      dest_tiles[i*tiles_h + 1].dest_size.width -= diff_h
   1.314 +      dest_tiles[i*tiles_h + 1].offset.x -= diff_h #* tiles[4].scale.x
   1.315 +      dest_tiles[(i+1)*tiles_h-2].dest_size.width -= diff_h
   1.316 +
   1.317 +  # output the table to simulate the border
   1.318 +  print "<table>"
   1.319 +  for i in range(tiles_h):
   1.320 +    print "<col style=\"width: " + str(dest_tiles[i].dest_size.width) + "px;\">"
   1.321 +  for i in range(tiles_v):
   1.322 +    print "<tr style=\"height: " + str(dest_tiles[i*tiles_h].dest_size.height) + "px;\">"
   1.323 +    for j in range(tiles_h):
   1.324 +      width = dest_tiles[i*tiles_h+j].size.width
   1.325 +      height = dest_tiles[i*tiles_h+j].size.height
   1.326 +      # catch any tiles with negative widths/heights
   1.327 +      # this happends when the total of the border-image-slices > borde drawing area
   1.328 +      if width <= 0 or height <= 0:
   1.329 +        print "  <td style=\"background: white;\"></td>"
   1.330 +      else:
   1.331 +        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>"
   1.332 +    print "</tr>"
   1.333 +  print "</table>"
   1.334 +
   1.335 +
   1.336 +# start here
   1.337 +args = sys.argv[1:]
   1.338 +if len(args) == 0:
   1.339 +  print "whoops: no source file"
   1.340 +  exit(1)
   1.341 +
   1.342 +
   1.343 +props = parse(args[0])
   1.344 +if not check_parse(props):
   1.345 +  print dir(props)
   1.346 +  exit(1)
   1.347 +props = normalise(props)
   1.348 +if not check_normalise(props):
   1.349 +  exit(1)
   1.350 +compute(props)

mercurial