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

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

     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/.
     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.
    30 import sys
    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
    51 class Props:
    52   def __init__(self):
    53     self.size = Size()
    55 class np:
    56   def __init__(self, n, p):
    57     self.n = n
    58     self.p = p
    60   def get_absolute(self, ref):
    61     if not self.p == 0:
    62       return self.p
    63     return self.n * ref
    65 def parse_p(tok):
    66   if tok[-2:] == "px":
    67     return float(tok[:-2])
    68   print "Whoops, not a pixel value " + tok
    70 def parse_np(tok):
    71   if tok[-2:] == "px":
    72     return np(0, float(tok[:-2]))
    73   return np(float(tok), 0)
    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
   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
   120   return result
   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
   148   return True
   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
   167   return True
   169 class Tile:
   170   def __init__(self):
   171     self.slice = Rect()
   172     self.border_width = Rect()
   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
   179 # Compute the source tiles' slice and border-width sizes
   180 def make_src_tiles():
   181   tiles = [Tile() for i in range(9)]
   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)]
   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]
   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]
   204   return tiles
   206 def compute(props):
   207   tiles = make_src_tiles()
   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)
   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)
   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"
   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
   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)
   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)]
   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)
   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)
   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)
   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
   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
   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>"
   333 # start here
   334 args = sys.argv[1:]
   335 if len(args) == 0:
   336   print "whoops: no source file"
   337   exit(1)
   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)

mercurial