474 lines
16 KiB
Python
474 lines
16 KiB
Python
# 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)
|