# 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("") for i in range(tiles_h): print('') for i in range(tiles_v): print( '' ) 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(' ') else: print( ' ' ) print("") print("
") # 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)