tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

gen-refs.py (16000B)


      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/.
      4 
      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.
     29 
     30 import sys
     31 
     32 
     33 class Point:
     34    def __init__(self, w=0, h=0):
     35        self.x = w
     36        self.y = h
     37 
     38 
     39 class Size:
     40    def __init__(self, w=0, h=0):
     41        self.width = w
     42        self.height = h
     43 
     44 
     45 class Rect:
     46    def __init__(self, x=0, y=0, x2=0, y2=0):
     47        self.x = x
     48        self.y = y
     49        self.x2 = x2
     50        self.y2 = y2
     51 
     52    def width(self):
     53        return self.x2 - self.x
     54 
     55    def height(self):
     56        return self.y2 - self.y
     57 
     58 
     59 class Props:
     60    def __init__(self):
     61        self.size = Size()
     62 
     63 
     64 class np:
     65    def __init__(self, n, p):
     66        self.n = n
     67        self.p = p
     68 
     69    def get_absolute(self, ref):
     70        if not self.p == 0:
     71            return self.p
     72        return self.n * ref
     73 
     74 
     75 def parse_p(tok):
     76    if tok[-2:] == "px":
     77        return float(tok[:-2])
     78    print("Whoops, not a pixel value", tok)
     79 
     80 
     81 def parse_np(tok):
     82    if tok[-2:] == "px":
     83        return np(0, float(tok[:-2]))
     84    return np(float(tok), 0)
     85 
     86 
     87 def parse(filename):
     88    f = open(filename, "r")
     89    props = Props()
     90    for l in f:
     91        l = l.strip()
     92        if not l[-1] == ";":
     93            continue
     94        toks = l[:-1].split()
     95        if toks[0] == "border-width:":
     96            props.width = parse_p(toks[1])
     97        if toks[0] == "height:":
     98            props.size.height = parse_p(toks[1])
     99        if toks[0] == "width:":
    100            props.size.width = parse_p(toks[1])
    101        if toks[0] == "border-image-source:":
    102            props.source = l[l.find(":") + 1 : l.rfind(";")].strip()
    103        if toks[0] == "border-image-repeat:":
    104            props.repeat = toks[1]
    105        if toks[0] == "border-image-slice:":
    106            props.slice = map(parse_p, toks[1:5])
    107        if toks[0] == "border-image-width:":
    108            props.image_width = map(parse_np, toks[1:5])
    109        if toks[0] == "border-image-outset:":
    110            props.outset = map(parse_np, toks[1:5])
    111    f.close()
    112    return props
    113 
    114 
    115 # the result of normalisation is that all sizes are in pixels and the size,
    116 # widths, and outset have been normalised to a size and width - the former is
    117 # the element's interior, the latter is the width of the drawn border.
    118 def normalise(props):
    119    result = Props()
    120    result.source = props.source
    121    result.repeat = props.repeat
    122    result.width = map(lambda x: x.get_absolute(props.width), props.image_width)
    123    outsets = map(lambda x: x.get_absolute(props.width), props.outset)
    124    result.size.width = props.size.width + 2 * props.width + outsets[1] + outsets[3]
    125    result.size.height = props.size.height + 2 * props.width + outsets[0] + outsets[2]
    126    result.slice = props.slice
    127    for i in [0, 2]:
    128        if result.slice[i] > result.size.height:
    129            result.slice[i] = result.size.height
    130        if result.slice[i + 1] > result.size.width:
    131            result.slice[i + 1] = result.size.width
    132 
    133    return result
    134 
    135 
    136 def check_parse(props):
    137    if not hasattr(props, "source"):
    138        print("missing border-image-source")
    139        return False
    140    if not hasattr(props.size, "width"):
    141        print("missing width")
    142        return False
    143    if not hasattr(props.size, "height"):
    144        print("missing height")
    145        return False
    146    if not hasattr(props, "width"):
    147        print("missing border-width")
    148        return False
    149    if not hasattr(props, "image_width"):
    150        print("missing border-image-width")
    151        return False
    152    if not hasattr(props, "slice"):
    153        print("missing border-image-slice")
    154        return False
    155    if not hasattr(props, "repeat") or (
    156        props.repeat not in ["stretch", "repeat", "round"]
    157    ):
    158        print("missing or incorrect border-image-repeat '" + props.repeat + "'")
    159        return False
    160    if not hasattr(props, "outset"):
    161        print("missing border-image-outset")
    162        return False
    163 
    164    return True
    165 
    166 
    167 def check_normalise(props):
    168    if not hasattr(props, "source"):
    169        print("missing border-image-source")
    170        return False
    171    if not hasattr(props.size, "width"):
    172        print("missing width")
    173        return False
    174    if not hasattr(props.size, "height"):
    175        print("missing height")
    176        return False
    177    if not hasattr(props, "slice"):
    178        print("missing border-image-slice")
    179        return False
    180    if not hasattr(props, "repeat") or (
    181        props.repeat not in ["stretch", "repeat", "round"]
    182    ):
    183        print("missing or incorrect border-image-repeat '" + props.repeat + "'")
    184        return False
    185 
    186    return True
    187 
    188 
    189 class Tile:
    190    def __init__(self):
    191        self.slice = Rect()
    192        self.border_width = Rect()
    193 
    194 
    195 # throughout, we will use arrays for nine-patches, the indices correspond thusly:
    196 # 0 1 2
    197 # 3 4 5
    198 # 6 7 8
    199 
    200 
    201 # Compute the source tiles' slice and border-width sizes
    202 def make_src_tiles():
    203    tiles = [Tile() for i in range(9)]
    204 
    205    rows = [range(3 * i, 3 * (i + 1)) for i in range(3)]
    206    cols = [[i, i + 3, i + 6] for i in range(3)]
    207 
    208    row_limits_slice = [
    209        0,
    210        props.slice[3],
    211        props.size.width - props.slice[1],
    212        props.size.width,
    213    ]
    214    row_limits_width = [
    215        0,
    216        props.width[3],
    217        props.size.width - props.width[1],
    218        props.size.width,
    219    ]
    220    for r in range(3):
    221        for t in [tiles[i] for i in cols[r]]:
    222            t.slice.x = row_limits_slice[r]
    223            t.slice.x2 = row_limits_slice[r + 1]
    224            t.border_width.x = row_limits_width[r]
    225            t.border_width.x2 = row_limits_width[r + 1]
    226 
    227    col_limits_slice = [
    228        0,
    229        props.slice[0],
    230        props.size.height - props.slice[2],
    231        props.size.height,
    232    ]
    233    col_limits_width = [
    234        0,
    235        props.width[0],
    236        props.size.height - props.width[2],
    237        props.size.height,
    238    ]
    239    for c in range(3):
    240        for t in [tiles[i] for i in rows[c]]:
    241            t.slice.y = col_limits_slice[c]
    242            t.slice.y2 = col_limits_slice[c + 1]
    243            t.border_width.y = col_limits_width[c]
    244            t.border_width.y2 = col_limits_width[c + 1]
    245 
    246    return tiles
    247 
    248 
    249 def compute(props):
    250    tiles = make_src_tiles()
    251 
    252    # corners scale easy
    253    for t in [tiles[i] for i in [0, 2, 6, 8]]:
    254        t.scale = Point(
    255            t.border_width.width() / t.slice.width(),
    256            t.border_width.height() / t.slice.height(),
    257        )
    258    # edges are by their secondary dimension
    259    for t in [tiles[i] for i in [1, 7]]:
    260        t.scale = Point(
    261            t.border_width.height() / t.slice.height(),
    262            t.border_width.height() / t.slice.height(),
    263        )
    264    for t in [tiles[i] for i in [3, 5]]:
    265        t.scale = Point(
    266            t.border_width.width() / t.slice.width(),
    267            t.border_width.width() / t.slice.width(),
    268        )
    269    # the middle is scaled by the factors for the top and left edges
    270    tiles[4].scale = Point(tiles[1].scale.x, tiles[3].scale.y)
    271 
    272    # the size of a source tile for the middle section
    273    src_tile_size = Size(
    274        tiles[4].slice.width() * tiles[4].scale.x,
    275        tiles[4].slice.height() * tiles[4].scale.y,
    276    )
    277 
    278    # the size of a single destination tile in the central part
    279    dest_tile_size = Size()
    280    if props.repeat == "stretch":
    281        dest_tile_size.width = tiles[4].border_width.width()
    282        dest_tile_size.height = tiles[4].border_width.height()
    283        for t in [tiles[i] for i in [1, 7]]:
    284            t.scale.x = t.border_width.width() / t.slice.width()
    285        for t in [tiles[i] for i in [3, 5]]:
    286            t.scale.y = t.border_width.height() / t.slice.height()
    287    elif props.repeat == "repeat":
    288        dest_tile_size = src_tile_size
    289    elif props.repeat == "round":
    290        dest_tile_size.width = tiles[4].border_width.width() / math.ceil(
    291            tiles[4].border_width.width() / src_tile_size.width
    292        )
    293        dest_tile_size.height = tiles[4].border_width.height() / math.ceil(
    294            tiles[4].border_width.height() / src_tile_size.height
    295        )
    296        for t in [tiles[i] for i in [1, 4, 7]]:
    297            t.scale.x = dest_tile_size.width / t.slice.width()
    298        for t in [tiles[i] for i in [3, 4, 5]]:
    299            t.scale.y = dest_tile_size.height / t.slice.height()
    300    else:
    301        print("Whoops, invalid border-image-repeat value")
    302 
    303    # catch overlapping slices. Its easier to deal with it here than to catch
    304    # earlier and have to avoid all the divide by zeroes above
    305    for t in tiles:
    306        if t.slice.width() < 0:
    307            t.scale.x = 0
    308        if t.slice.height() < 0:
    309            t.scale.y = 0
    310 
    311    tiles_h = int(math.ceil(tiles[4].border_width.width() / dest_tile_size.width) + 2)
    312    tiles_v = int(math.ceil(tiles[4].border_width.height() / dest_tile_size.height) + 2)
    313 
    314    # if border-image-repeat: repeat, then we will later center the tiles, that
    315    # means we need an extra tile for the two 'half' tiles at either end
    316    if props.repeat == "repeat":
    317        if tiles_h % 2 == 0:
    318            tiles_h += 1
    319        if tiles_v % 2 == 0:
    320            tiles_v += 1
    321    dest_tiles = [Tile() for i in range(tiles_h * tiles_v)]
    322 
    323    # corners
    324    corners = [
    325        (0, 0),
    326        (tiles_h - 1, 2),
    327        (tiles_v * (tiles_h - 1), 6),
    328        (tiles_v * tiles_h - 1, 8),
    329    ]
    330    for d, s in corners:
    331        dest_tiles[d].size = Size(
    332            tiles[s].scale.x * props.size.width, tiles[s].scale.y * props.size.height
    333        )
    334        dest_tiles[d].dest_size = Size(
    335            tiles[s].border_width.width(), tiles[s].border_width.height()
    336        )
    337    dest_tiles[0].offset = Point(0, 0)
    338    dest_tiles[tiles_h - 1].offset = Point(
    339        tiles[2].border_width.width() - dest_tiles[tiles_h - 1].size.width, 0
    340    )
    341    dest_tiles[tiles_v * (tiles_h - 1)].offset = Point(
    342        0,
    343        tiles[6].border_width.height()
    344        - dest_tiles[tiles_v * (tiles_h - 1)].size.height,
    345    )
    346    dest_tiles[tiles_v * tiles_h - 1].offset = Point(
    347        tiles[8].border_width.width() - dest_tiles[tiles_h * tiles_v - 1].size.width,
    348        tiles[8].border_width.height() - dest_tiles[tiles_h * tiles_v - 1].size.height,
    349    )
    350 
    351    # horizontal edges
    352    for i in range(1, tiles_h - 1):
    353        dest_tiles[i].size = Size(
    354            tiles[1].scale.x * props.size.width, tiles[1].scale.y * props.size.height
    355        )
    356        dest_tiles[(tiles_v - 1) * tiles_h + i].size = Size(
    357            tiles[7].scale.x * props.size.width, tiles[7].scale.y * props.size.height
    358        )
    359        dest_tiles[i].dest_size = Size(
    360            dest_tile_size.width, tiles[1].border_width.height()
    361        )
    362        dest_tiles[(tiles_v - 1) * tiles_h + i].dest_size = Size(
    363            dest_tile_size.width, tiles[7].border_width.height()
    364        )
    365        dest_tiles[i].offset = Point(
    366            -tiles[1].scale.x * tiles[1].slice.x, -tiles[1].scale.y * tiles[1].slice.y
    367        )
    368        dest_tiles[(tiles_v - 1) * tiles_h + i].offset = Point(
    369            -tiles[7].scale.x * tiles[7].slice.x, -tiles[7].scale.y * tiles[7].slice.y
    370        )
    371 
    372    # vertical edges
    373    for i in range(1, tiles_v - 1):
    374        dest_tiles[i * tiles_h].size = Size(
    375            tiles[3].scale.x * props.size.width, tiles[3].scale.y * props.size.height
    376        )
    377        dest_tiles[(i + 1) * tiles_h - 1].size = Size(
    378            tiles[5].scale.x * props.size.width, tiles[5].scale.y * props.size.height
    379        )
    380        dest_tiles[i * tiles_h].dest_size = Size(
    381            tiles[3].border_width.width(), dest_tile_size.height
    382        )
    383        dest_tiles[(i + 1) * tiles_h - 1].dest_size = Size(
    384            tiles[5].border_width.width(), dest_tile_size.height
    385        )
    386        dest_tiles[i * tiles_h].offset = Point(
    387            -tiles[3].scale.x * tiles[3].slice.x, -tiles[3].scale.y * tiles[3].slice.y
    388        )
    389        dest_tiles[(i + 1) * tiles_h - 1].offset = Point(
    390            -tiles[5].scale.x * tiles[5].slice.x, -tiles[5].scale.y * tiles[5].slice.y
    391        )
    392 
    393    # middle
    394    for i in range(1, tiles_v - 1):
    395        for j in range(1, tiles_h - 1):
    396            dest_tiles[i * tiles_h + j].size = Size(
    397                tiles[4].scale.x * props.size.width,
    398                tiles[4].scale.y * props.size.height,
    399            )
    400            dest_tiles[i * tiles_h + j].offset = Point(
    401                -tiles[4].scale.x * tiles[4].slice.x,
    402                -tiles[4].scale.y * tiles[4].slice.y,
    403            )
    404            dest_tiles[i * tiles_h + j].dest_size = dest_tile_size
    405 
    406    # edge and middle tiles are centered with border-image-repeat: repeat
    407    # we need to change the offset to take account of this and change the dest_size
    408    # of the tiles at the sides of the edges if they are clipped
    409    if props.repeat == "repeat":
    410        diff_h = (
    411            (tiles_h - 2) * dest_tile_size.width - tiles[4].border_width.width()
    412        ) / 2
    413        diff_v = (
    414            (tiles_v - 2) * dest_tile_size.height - tiles[4].border_width.height()
    415        ) / 2
    416        for i in range(0, tiles_h):
    417            dest_tiles[tiles_h + i].dest_size.height -= diff_v
    418            dest_tiles[tiles_h + i].offset.y -= diff_v  # * tiles[4].scale.y
    419            dest_tiles[(tiles_v - 2) * tiles_h + i].dest_size.height -= diff_v
    420        for i in range(0, tiles_v):
    421            dest_tiles[i * tiles_h + 1].dest_size.width -= diff_h
    422            dest_tiles[i * tiles_h + 1].offset.x -= diff_h  # * tiles[4].scale.x
    423            dest_tiles[(i + 1) * tiles_h - 2].dest_size.width -= diff_h
    424 
    425    # output the table to simulate the border
    426    print("<table>")
    427    for i in range(tiles_h):
    428        print('<col style="width: ' + str(dest_tiles[i].dest_size.width) + 'px;">')
    429    for i in range(tiles_v):
    430        print(
    431            '<tr style="height: '
    432            + str(dest_tiles[i * tiles_h].dest_size.height)
    433            + 'px;">'
    434        )
    435        for j in range(tiles_h):
    436            width = dest_tiles[i * tiles_h + j].size.width
    437            height = dest_tiles[i * tiles_h + j].size.height
    438            # catch any tiles with negative widths/heights
    439            # this happends when the total of the border-image-slices > borde drawing area
    440            if width <= 0 or height <= 0:
    441                print('  <td style="background: white;"></td>')
    442            else:
    443                print(
    444                    '  <td style="background-image: '
    445                    + props.source
    446                    + "; background-size: "
    447                    + str(width)
    448                    + "px "
    449                    + str(height)
    450                    + "px; background-position: "
    451                    + str(dest_tiles[i * tiles_h + j].offset.x)
    452                    + "px "
    453                    + str(dest_tiles[i * tiles_h + j].offset.y)
    454                    + 'px;"></td>'
    455                )
    456        print("</tr>")
    457    print("</table>")
    458 
    459 
    460 # start here
    461 args = sys.argv[1:]
    462 if len(args) == 0:
    463    print("whoops: no source file")
    464    exit(1)
    465 
    466 
    467 props = parse(args[0])
    468 if not check_parse(props):
    469    print(dir(props))
    470    exit(1)
    471 props = normalise(props)
    472 if not check_normalise(props):
    473    exit(1)
    474 compute(props)