diff options
Diffstat (limited to 'layout/reftests/border-image/gen-refs.py')
-rw-r--r-- | layout/reftests/border-image/gen-refs.py | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/layout/reftests/border-image/gen-refs.py b/layout/reftests/border-image/gen-refs.py new file mode 100644 index 0000000000..029d46f2a8 --- /dev/null +++ b/layout/reftests/border-image/gen-refs.py @@ -0,0 +1,473 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Generates tables of background images which correspond with border images for +# creating reftests. Input is the filename containing input defined below (a subset +# of the allowed CSS border properties). An html representation of a table is +# output to stdout. +# +# Usage: python gen-refs.py input_filename +# +# Input must take the form (order is not important, nothing is optional, distance in order top, right, bottom, left): +# width: p; +# height: p; +# border-width: p; +# border-image-source: ...; +# border-image-slice: p p p p; +# note that actually border-image-slice takes numbers without px, which represent pixels anyway (or at least coords) +# border-image-width: np np np np; +# border-image-repeat: stretch | repeat | round; +# border-image-outset: np np np np; +# +# where: +# p ::= n'px' +# np ::= n | p +# +# Assumes there is no intrinsic size for the border-image-source, so uses +# the size of the border image area. + +import sys + + +class Point: + def __init__(self, w=0, h=0): + self.x = w + self.y = h + + +class Size: + def __init__(self, w=0, h=0): + self.width = w + self.height = h + + +class Rect: + def __init__(self, x=0, y=0, x2=0, y2=0): + self.x = x + self.y = y + self.x2 = x2 + self.y2 = y2 + + def width(self): + return self.x2 - self.x + + def height(self): + return self.y2 - self.y + + +class Props: + def __init__(self): + self.size = Size() + + +class np: + def __init__(self, n, p): + self.n = n + self.p = p + + def get_absolute(self, ref): + if not self.p == 0: + return self.p + return self.n * ref + + +def parse_p(tok): + if tok[-2:] == "px": + return float(tok[:-2]) + print("Whoops, not a pixel value", tok) + + +def parse_np(tok): + if tok[-2:] == "px": + return np(0, float(tok[:-2])) + return np(float(tok), 0) + + +def parse(filename): + f = open(filename, "r") + props = Props() + for l in f: + l = l.strip() + if not l[-1] == ";": + continue + toks = l[:-1].split() + if toks[0] == "border-width:": + props.width = parse_p(toks[1]) + if toks[0] == "height:": + props.size.height = parse_p(toks[1]) + if toks[0] == "width:": + props.size.width = parse_p(toks[1]) + if toks[0] == "border-image-source:": + props.source = l[l.find(":") + 1 : l.rfind(";")].strip() + if toks[0] == "border-image-repeat:": + props.repeat = toks[1] + if toks[0] == "border-image-slice:": + props.slice = map(parse_p, toks[1:5]) + if toks[0] == "border-image-width:": + props.image_width = map(parse_np, toks[1:5]) + if toks[0] == "border-image-outset:": + props.outset = map(parse_np, toks[1:5]) + f.close() + return props + + +# the result of normalisation is that all sizes are in pixels and the size, +# widths, and outset have been normalised to a size and width - the former is +# the element's interior, the latter is the width of the drawn border. +def normalise(props): + result = Props() + result.source = props.source + result.repeat = props.repeat + result.width = map(lambda x: x.get_absolute(props.width), props.image_width) + outsets = map(lambda x: x.get_absolute(props.width), props.outset) + result.size.width = props.size.width + 2 * props.width + outsets[1] + outsets[3] + result.size.height = props.size.height + 2 * props.width + outsets[0] + outsets[2] + result.slice = props.slice + for i in [0, 2]: + if result.slice[i] > result.size.height: + result.slice[i] = result.size.height + if result.slice[i + 1] > result.size.width: + result.slice[i + 1] = result.size.width + + return result + + +def check_parse(props): + if not hasattr(props, "source"): + print("missing border-image-source") + return False + if not hasattr(props.size, "width"): + print("missing width") + return False + if not hasattr(props.size, "height"): + print("missing height") + return False + if not hasattr(props, "width"): + print("missing border-width") + return False + if not hasattr(props, "image_width"): + print("missing border-image-width") + return False + if not hasattr(props, "slice"): + print("missing border-image-slice") + return False + if not hasattr(props, "repeat") or ( + props.repeat not in ["stretch", "repeat", "round"] + ): + print("missing or incorrect border-image-repeat '" + props.repeat + "'") + return False + if not hasattr(props, "outset"): + print("missing border-image-outset") + return False + + return True + + +def check_normalise(props): + if not hasattr(props, "source"): + print("missing border-image-source") + return False + if not hasattr(props.size, "width"): + print("missing width") + return False + if not hasattr(props.size, "height"): + print("missing height") + return False + if not hasattr(props, "slice"): + print("missing border-image-slice") + return False + if not hasattr(props, "repeat") or ( + props.repeat not in ["stretch", "repeat", "round"] + ): + print("missing or incorrect border-image-repeat '" + props.repeat + "'") + return False + + return True + + +class Tile: + def __init__(self): + self.slice = Rect() + self.border_width = Rect() + + +# throughout, we will use arrays for nine-patches, the indices correspond thusly: +# 0 1 2 +# 3 4 5 +# 6 7 8 + +# Compute the source tiles' slice and border-width sizes +def make_src_tiles(): + tiles = [Tile() for i in range(9)] + + rows = [range(3 * i, 3 * (i + 1)) for i in range(3)] + cols = [[i, i + 3, i + 6] for i in range(3)] + + row_limits_slice = [ + 0, + props.slice[3], + props.size.width - props.slice[1], + props.size.width, + ] + row_limits_width = [ + 0, + props.width[3], + props.size.width - props.width[1], + props.size.width, + ] + for r in range(3): + for t in [tiles[i] for i in cols[r]]: + t.slice.x = row_limits_slice[r] + t.slice.x2 = row_limits_slice[r + 1] + t.border_width.x = row_limits_width[r] + t.border_width.x2 = row_limits_width[r + 1] + + col_limits_slice = [ + 0, + props.slice[0], + props.size.height - props.slice[2], + props.size.height, + ] + col_limits_width = [ + 0, + props.width[0], + props.size.height - props.width[2], + props.size.height, + ] + for c in range(3): + for t in [tiles[i] for i in rows[c]]: + t.slice.y = col_limits_slice[c] + t.slice.y2 = col_limits_slice[c + 1] + t.border_width.y = col_limits_width[c] + t.border_width.y2 = col_limits_width[c + 1] + + return tiles + + +def compute(props): + tiles = make_src_tiles() + + # corners scale easy + for t in [tiles[i] for i in [0, 2, 6, 8]]: + t.scale = Point( + t.border_width.width() / t.slice.width(), + t.border_width.height() / t.slice.height(), + ) + # edges are by their secondary dimension + for t in [tiles[i] for i in [1, 7]]: + t.scale = Point( + t.border_width.height() / t.slice.height(), + t.border_width.height() / t.slice.height(), + ) + for t in [tiles[i] for i in [3, 5]]: + t.scale = Point( + t.border_width.width() / t.slice.width(), + t.border_width.width() / t.slice.width(), + ) + # the middle is scaled by the factors for the top and left edges + tiles[4].scale = Point(tiles[1].scale.x, tiles[3].scale.y) + + # the size of a source tile for the middle section + src_tile_size = Size( + tiles[4].slice.width() * tiles[4].scale.x, + tiles[4].slice.height() * tiles[4].scale.y, + ) + + # the size of a single destination tile in the central part + dest_tile_size = Size() + if props.repeat == "stretch": + dest_tile_size.width = tiles[4].border_width.width() + dest_tile_size.height = tiles[4].border_width.height() + for t in [tiles[i] for i in [1, 7]]: + t.scale.x = t.border_width.width() / t.slice.width() + for t in [tiles[i] for i in [3, 5]]: + t.scale.y = t.border_width.height() / t.slice.height() + elif props.repeat == "repeat": + dest_tile_size = src_tile_size + elif props.repeat == "round": + dest_tile_size.width = tiles[4].border_width.width() / math.ceil( + tiles[4].border_width.width() / src_tile_size.width + ) + dest_tile_size.height = tiles[4].border_width.height() / math.ceil( + tiles[4].border_width.height() / src_tile_size.height + ) + for t in [tiles[i] for i in [1, 4, 7]]: + t.scale.x = dest_tile_size.width / t.slice.width() + for t in [tiles[i] for i in [3, 4, 5]]: + t.scale.y = dest_tile_size.height / t.slice.height() + else: + print("Whoops, invalid border-image-repeat value") + + # catch overlapping slices. Its easier to deal with it here than to catch + # earlier and have to avoid all the divide by zeroes above + for t in tiles: + if t.slice.width() < 0: + t.scale.x = 0 + if t.slice.height() < 0: + t.scale.y = 0 + + tiles_h = int(math.ceil(tiles[4].border_width.width() / dest_tile_size.width) + 2) + tiles_v = int(math.ceil(tiles[4].border_width.height() / dest_tile_size.height) + 2) + + # if border-image-repeat: repeat, then we will later center the tiles, that + # means we need an extra tile for the two 'half' tiles at either end + if props.repeat == "repeat": + if tiles_h % 2 == 0: + tiles_h += 1 + if tiles_v % 2 == 0: + tiles_v += 1 + dest_tiles = [Tile() for i in range(tiles_h * tiles_v)] + + # corners + corners = [ + (0, 0), + (tiles_h - 1, 2), + (tiles_v * (tiles_h - 1), 6), + (tiles_v * tiles_h - 1, 8), + ] + for d, s in corners: + dest_tiles[d].size = Size( + tiles[s].scale.x * props.size.width, tiles[s].scale.y * props.size.height + ) + dest_tiles[d].dest_size = Size( + tiles[s].border_width.width(), tiles[s].border_width.height() + ) + dest_tiles[0].offset = Point(0, 0) + dest_tiles[tiles_h - 1].offset = Point( + tiles[2].border_width.width() - dest_tiles[tiles_h - 1].size.width, 0 + ) + dest_tiles[tiles_v * (tiles_h - 1)].offset = Point( + 0, + tiles[6].border_width.height() + - dest_tiles[tiles_v * (tiles_h - 1)].size.height, + ) + 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, + ) + + # horizontal edges + for i in range(1, tiles_h - 1): + dest_tiles[i].size = Size( + tiles[1].scale.x * props.size.width, tiles[1].scale.y * props.size.height + ) + dest_tiles[(tiles_v - 1) * tiles_h + i].size = Size( + tiles[7].scale.x * props.size.width, tiles[7].scale.y * props.size.height + ) + dest_tiles[i].dest_size = Size( + dest_tile_size.width, tiles[1].border_width.height() + ) + dest_tiles[(tiles_v - 1) * tiles_h + i].dest_size = Size( + dest_tile_size.width, tiles[7].border_width.height() + ) + dest_tiles[i].offset = Point( + -tiles[1].scale.x * tiles[1].slice.x, -tiles[1].scale.y * tiles[1].slice.y + ) + 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 + ) + + # vertical edges + for i in range(1, tiles_v - 1): + dest_tiles[i * tiles_h].size = Size( + tiles[3].scale.x * props.size.width, tiles[3].scale.y * props.size.height + ) + dest_tiles[(i + 1) * tiles_h - 1].size = Size( + tiles[5].scale.x * props.size.width, tiles[5].scale.y * props.size.height + ) + dest_tiles[i * tiles_h].dest_size = Size( + tiles[3].border_width.width(), dest_tile_size.height + ) + dest_tiles[(i + 1) * tiles_h - 1].dest_size = Size( + tiles[5].border_width.width(), dest_tile_size.height + ) + dest_tiles[i * tiles_h].offset = Point( + -tiles[3].scale.x * tiles[3].slice.x, -tiles[3].scale.y * tiles[3].slice.y + ) + 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 + ) + + # middle + for i in range(1, tiles_v - 1): + for j in range(1, tiles_h - 1): + dest_tiles[i * tiles_h + j].size = Size( + tiles[4].scale.x * props.size.width, + tiles[4].scale.y * props.size.height, + ) + dest_tiles[i * tiles_h + j].offset = Point( + -tiles[4].scale.x * tiles[4].slice.x, + -tiles[4].scale.y * tiles[4].slice.y, + ) + dest_tiles[i * tiles_h + j].dest_size = dest_tile_size + + # edge and middle tiles are centered with border-image-repeat: repeat + # we need to change the offset to take account of this and change the dest_size + # of the tiles at the sides of the edges if they are clipped + if props.repeat == "repeat": + diff_h = ( + (tiles_h - 2) * dest_tile_size.width - tiles[4].border_width.width() + ) / 2 + diff_v = ( + (tiles_v - 2) * dest_tile_size.height - tiles[4].border_width.height() + ) / 2 + for i in range(0, tiles_h): + dest_tiles[tiles_h + i].dest_size.height -= diff_v + dest_tiles[tiles_h + i].offset.y -= diff_v # * tiles[4].scale.y + dest_tiles[(tiles_v - 2) * tiles_h + i].dest_size.height -= diff_v + for i in range(0, tiles_v): + dest_tiles[i * tiles_h + 1].dest_size.width -= diff_h + dest_tiles[i * tiles_h + 1].offset.x -= diff_h # * tiles[4].scale.x + dest_tiles[(i + 1) * tiles_h - 2].dest_size.width -= diff_h + + # output the table to simulate the border + print("<table>") + for i in range(tiles_h): + print('<col style="width: ' + str(dest_tiles[i].dest_size.width) + 'px;">') + for i in range(tiles_v): + print( + '<tr style="height: ' + + str(dest_tiles[i * tiles_h].dest_size.height) + + 'px;">' + ) + for j in range(tiles_h): + width = dest_tiles[i * tiles_h + j].size.width + height = dest_tiles[i * tiles_h + j].size.height + # catch any tiles with negative widths/heights + # this happends when the total of the border-image-slices > borde drawing area + if width <= 0 or height <= 0: + print(' <td style="background: white;"></td>') + else: + 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>' + ) + print("</tr>") + print("</table>") + + +# start here +args = sys.argv[1:] +if len(args) == 0: + print("whoops: no source file") + exit(1) + + +props = parse(args[0]) +if not check_parse(props): + print(dir(props)) + exit(1) +props = normalise(props) +if not check_normalise(props): + exit(1) +compute(props) |