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