Sebastian Kuschel reports that pfd_closing can be called for a socket
[u/mdw/putty] / icons / mkicon.py
index 5bb6a1c..3e6d81c 100755 (executable)
@@ -20,28 +20,28 @@ def overlay(src, x, y, dst):
     x = int(x)
     y = int(y)
     for (sx, sy), colour in src.items():
-       dst[sx+x, sy+y] = blend(colour, dst.get((sx+x, sy+y), cT))
+        dst[sx+x, sy+y] = blend(colour, dst.get((sx+x, sy+y), cT))
 
 def finalise(canvas):
     for k in canvas.keys():
-       canvas[k] = finalisepix(canvas[k])
+        canvas[k] = finalisepix(canvas[k])
 
 def bbox(canvas):
     minx, miny, maxx, maxy = None, None, None, None
     for (x, y) in canvas.keys():
-       if minx == None:
-           minx, miny, maxx, maxy = x, y, x+1, y+1
-       else:
-           minx = min(minx, x)
-           miny = min(miny, y)
-           maxx = max(maxx, x+1)
-           maxy = max(maxy, y+1)
+        if minx == None:
+            minx, miny, maxx, maxy = x, y, x+1, y+1
+        else:
+            minx = min(minx, x)
+            miny = min(miny, y)
+            maxx = max(maxx, x+1)
+            maxy = max(maxy, y+1)
     return (minx, miny, maxx, maxy)
 
 def topy(canvas):
     miny = {}
     for (x, y) in canvas.keys():
-       miny[x] = min(miny.get(x, y), y)
+        miny[x] = min(miny.get(x, y), y)
     return miny
 
 def render(canvas, minx, miny, maxx, maxy):
@@ -49,10 +49,10 @@ def render(canvas, minx, miny, maxx, maxy):
     h = maxy - miny
     ret = []
     for y in range(h):
-       ret.append([outpix(cT)] * w)
+        ret.append([outpix(cT)] * w)
     for (x, y), colour in canvas.items():
-       if x >= minx and x < maxx and y >= miny and y < maxy:
-           ret[y-miny][x-minx] = outpix(colour)
+        if x >= minx and x < maxx and y >= miny and y < maxy:
+            ret[y-miny][x-minx] = outpix(colour)
     return ret
 
 # Code to actually draw pieces of icon. These don't generally worry
@@ -63,12 +63,12 @@ def render(canvas, minx, miny, maxx, maxy):
 sqrthash = {}
 def memoisedsqrt(x):
     if not sqrthash.has_key(x):
-       sqrthash[x] = math.sqrt(x)
+        sqrthash[x] = math.sqrt(x)
     return sqrthash[x]
 
 BR, TR, BL, TL = range(4) # enumeration of quadrants for border()
 
-def border(canvas, thickness, squarecorners):
+def border(canvas, thickness, squarecorners, out={}):
     # I haven't yet worked out exactly how to do borders in a
     # properly alpha-blended fashion.
     #
@@ -123,12 +123,14 @@ def border(canvas, thickness, squarecorners):
     thickness = memoisedsqrt(thickness)
 
     if thickness < 0.9:
-       darkness = 0.5
+        darkness = 0.5
     else:
-       darkness = 1
+        darkness = 1
     if thickness < 1: thickness = 1
     thickness = round(thickness - 0.5) + 0.3
 
+    out["borderthickness"] = thickness
+
     dmax = int(round(thickness))
     if dmax < thickness: dmax = dmax + 1
 
@@ -136,38 +138,40 @@ def border(canvas, thickness, squarecorners):
     squadrant = [[0] * (dmax+1) for x in range(dmax+1)]
 
     for x in range(dmax+1):
-       for y in range(dmax+1):
-           if max(x, y) < thickness:
-               squadrant[x][y] = darkness
-           if memoisedsqrt(x*x+y*y) < thickness:
-               cquadrant[x][y] = darkness
+        for y in range(dmax+1):
+            if max(x, y) < thickness:
+                squadrant[x][y] = darkness
+            if memoisedsqrt(x*x+y*y) < thickness:
+                cquadrant[x][y] = darkness
 
     bvalues = {}
     for (x, y), colour in canvas.items():
-       for dx in range(-dmax, dmax+1):
-           for dy in range(-dmax, dmax+1):
-               quadrant = 2 * (dx < 0) + (dy < 0)
-               if (x, y, quadrant) in squarecorners:
-                   bval = squadrant[abs(dx)][abs(dy)]
-               else:
-                   bval = cquadrant[abs(dx)][abs(dy)]
-               if bvalues.get((x+dx,y+dy),0) < bval:
-                   bvalues[(x+dx,y+dy)] = bval
+        for dx in range(-dmax, dmax+1):
+            for dy in range(-dmax, dmax+1):
+                quadrant = 2 * (dx < 0) + (dy < 0)
+                if (x, y, quadrant) in squarecorners:
+                    bval = squadrant[abs(dx)][abs(dy)]
+                else:
+                    bval = cquadrant[abs(dx)][abs(dy)]
+                if bvalues.get((x+dx,y+dy),0) < bval:
+                    bvalues[(x+dx,y+dy)] = bval
 
     for (x, y), value in bvalues.items():
-       if not canvas.has_key((x,y)):
-           canvas[(x,y)] = dark(value)
+        if not canvas.has_key((x,y)):
+            canvas[(x,y)] = dark(value)
 
-def sysbox(size):
+def sysbox(size, out={}):
     canvas = {}
 
     # The system box of the computer.
 
-    height = int(round(3*size))
-    width = int(round(17*size))
+    height = int(round(3.6*size))
+    width = int(round(16.51*size))
     depth = int(round(2*size))
     highlight = int(round(1*size))
-    bothighlight = int(round(0.49*size))
+    bothighlight = int(round(1*size))
+
+    out["sysboxheight"] = height
 
     floppystart = int(round(19*size)) # measured in half-pixels
     floppyend = int(round(29*size)) # measured in half-pixels
@@ -175,47 +179,47 @@ def sysbox(size):
     floppyrheight = 0.7 * size
     floppyheight = int(round(floppyrheight))
     if floppyheight < 1:
-       floppyheight = 1
+        floppyheight = 1
     floppytop = floppybottom - floppyheight
 
     # The front panel is rectangular.
     for x in range(width):
-       for y in range(height):
-           grey = 3
-           if x < highlight or y < highlight:
-               grey = grey + 1
-           if x >= width-highlight or y >= height-bothighlight:
-               grey = grey - 1
-           if y < highlight and x >= width-highlight:
-               v = (highlight-1-y) - (x-(width-highlight))
-               if v < 0:
-                   grey = grey - 1
-               elif v > 0:
-                   grey = grey + 1
-           if y >= floppytop and y < floppybottom and \
-           2*x+2 > floppystart and 2*x < floppyend:
-               if 2*x >= floppystart and 2*x+2 <= floppyend and \
-               floppyrheight >= 0.7:
-                   grey = 0
-               else:
-                   grey = 2
-           pixel(x, y, greypix(grey/4.0), canvas)
+        for y in range(height):
+            grey = 3
+            if x < highlight or y < highlight:
+                grey = grey + 1
+            if x >= width-highlight or y >= height-bothighlight:
+                grey = grey - 1
+            if y < highlight and x >= width-highlight:
+                v = (highlight-1-y) - (x-(width-highlight))
+                if v < 0:
+                    grey = grey - 1
+                elif v > 0:
+                    grey = grey + 1
+            if y >= floppytop and y < floppybottom and \
+            2*x+2 > floppystart and 2*x < floppyend:
+                if 2*x >= floppystart and 2*x+2 <= floppyend and \
+                floppyrheight >= 0.7:
+                    grey = 0
+                else:
+                    grey = 2
+            pixel(x, y, greypix(grey/4.0), canvas)
 
     # The side panel is a parallelogram.
     for x in range(depth):
-       for y in range(height+1):
-           pixel(x+width, y-(x+1), greypix(0.5), canvas)
+        for y in range(height):
+            pixel(x+width, y-(x+1), greypix(0.5), canvas)
 
     # The top panel is another parallelogram.
     for x in range(width-1):
-       for y in range(depth):
-           grey = 3
-           if x >= width-1 - highlight:
-               grey = grey + 1         
-           pixel(x+(y+1), -(y+1), greypix(grey/4.0), canvas)
+        for y in range(depth):
+            grey = 3
+            if x >= width-1 - highlight:
+                grey = grey + 1         
+            pixel(x+(y+1), -(y+1), greypix(grey/4.0), canvas)
 
     # And draw a border.
-    border(canvas, size, [])
+    border(canvas, size, [], out)
 
     return canvas
 
@@ -225,7 +229,7 @@ def monitor(size):
     # The computer's monitor.
 
     height = int(round(9.55*size))
-    width = int(round(11*size))
+    width = int(round(11.49*size))
     surround = int(round(1*size))
     botsurround = int(round(2*size))
     sheight = height - surround - botsurround
@@ -236,83 +240,83 @@ def monitor(size):
 
     # The front panel is rectangular.
     for x in range(width):
-       for y in range(height):
-           if x >= surround and y >= surround and \
-           x < surround+swidth and y < surround+sheight:
-               # Screen.
-               sx = (float(x-surround) - swidth/3) / swidth
-               sy = (float(y-surround) - sheight/3) / sheight
-               shighlight = 1.0 - (sx*sx+sy*sy)*0.27
-               pix = bluepix(shighlight)
-               if x < surround+shadow or y < surround+shadow:
-                   pix = blend(cD, pix) # sharp-edged shadow on top and left
-           else:
-               # Complicated double bevel on the screen surround.
-
-               # First, the outer bevel. We compute the distance
-               # from this pixel to each edge of the front
-               # rectangle.
-               list = [
-               (x, +1),
-               (y, +1),
-               (width-1-x, -1),
-               (height-1-y, -1)
-               ]
-               # Now sort the list to find the distance to the
-               # _nearest_ edge, or the two joint nearest.
-               list.sort()
-               # If there's one nearest edge, that determines our
-               # bevel colour. If there are two joint nearest, our
-               # bevel colour is their shared one if they agree,
-               # and neutral otherwise.
-               outerbevel = 0
-               if list[0][0] < list[1][0] or list[0][1] == list[1][1]:
-                   if list[0][0] < highlight:
-                       outerbevel = list[0][1]
-
-               # Now, the inner bevel. We compute the distance
-               # from this pixel to each edge of the screen
-               # itself.
-               list = [
-               (surround-1-x, -1),
-               (surround-1-y, -1),
-               (x-(surround+swidth), +1),
-               (y-(surround+sheight), +1)
-               ]
-               # Now we sort to find the _maximum_ distance, which
-               # conveniently ignores any less than zero.
-               list.sort()
-               # And now the strategy is pretty much the same as
-               # above, only we're working from the opposite end
-               # of the list.
-               innerbevel = 0
-               if list[-1][0] > list[-2][0] or list[-1][1] == list[-2][1]:
-                   if list[-1][0] >= 0 and list[-1][0] < highlight:
-                       innerbevel = list[-1][1]
-
-               # Now we know the adjustment we want to make to the
-               # pixel's overall grey shade due to the outer
-               # bevel, and due to the inner one. We break a tie
-               # in favour of a light outer bevel, but otherwise
-               # add.
-               grey = 3
-               if outerbevel > 0 or outerbevel == innerbevel:
-                   innerbevel = 0
-               grey = grey + outerbevel + innerbevel
-
-               pix = greypix(grey / 4.0)
-
-           pixel(x, y, pix, canvas)
+        for y in range(height):
+            if x >= surround and y >= surround and \
+            x < surround+swidth and y < surround+sheight:
+                # Screen.
+                sx = (float(x-surround) - swidth/3) / swidth
+                sy = (float(y-surround) - sheight/3) / sheight
+                shighlight = 1.0 - (sx*sx+sy*sy)*0.27
+                pix = bluepix(shighlight)
+                if x < surround+shadow or y < surround+shadow:
+                    pix = blend(cD, pix) # sharp-edged shadow on top and left
+            else:
+                # Complicated double bevel on the screen surround.
+
+                # First, the outer bevel. We compute the distance
+                # from this pixel to each edge of the front
+                # rectangle.
+                list = [
+                (x, +1),
+                (y, +1),
+                (width-1-x, -1),
+                (height-1-y, -1)
+                ]
+                # Now sort the list to find the distance to the
+                # _nearest_ edge, or the two joint nearest.
+                list.sort()
+                # If there's one nearest edge, that determines our
+                # bevel colour. If there are two joint nearest, our
+                # bevel colour is their shared one if they agree,
+                # and neutral otherwise.
+                outerbevel = 0
+                if list[0][0] < list[1][0] or list[0][1] == list[1][1]:
+                    if list[0][0] < highlight:
+                        outerbevel = list[0][1]
+
+                # Now, the inner bevel. We compute the distance
+                # from this pixel to each edge of the screen
+                # itself.
+                list = [
+                (surround-1-x, -1),
+                (surround-1-y, -1),
+                (x-(surround+swidth), +1),
+                (y-(surround+sheight), +1)
+                ]
+                # Now we sort to find the _maximum_ distance, which
+                # conveniently ignores any less than zero.
+                list.sort()
+                # And now the strategy is pretty much the same as
+                # above, only we're working from the opposite end
+                # of the list.
+                innerbevel = 0
+                if list[-1][0] > list[-2][0] or list[-1][1] == list[-2][1]:
+                    if list[-1][0] >= 0 and list[-1][0] < highlight:
+                        innerbevel = list[-1][1]
+
+                # Now we know the adjustment we want to make to the
+                # pixel's overall grey shade due to the outer
+                # bevel, and due to the inner one. We break a tie
+                # in favour of a light outer bevel, but otherwise
+                # add.
+                grey = 3
+                if outerbevel > 0 or outerbevel == innerbevel:
+                    innerbevel = 0
+                grey = grey + outerbevel + innerbevel
+
+                pix = greypix(grey / 4.0)
+
+            pixel(x, y, pix, canvas)
 
     # The side panel is a parallelogram.
     for x in range(depth):
-       for y in range(height):
-           pixel(x+width, y-x, greypix(0.5), canvas)
+        for y in range(height):
+            pixel(x+width, y-x, greypix(0.5), canvas)
 
     # The top panel is another parallelogram.
     for x in range(width):
-       for y in range(depth-1):
-           pixel(x+(y+1), -(y+1), greypix(0.75), canvas)
+        for y in range(depth-1):
+            pixel(x+(y+1), -(y+1), greypix(0.75), canvas)
 
     # And draw a border.
     border(canvas, size, [(0,int(height-1),BL)])
@@ -321,10 +325,11 @@ def monitor(size):
 
 def computer(size):
     # Monitor plus sysbox.
+    out = {}
     m = monitor(size)
-    s = sysbox(size)
+    s = sysbox(size, out)
     x = int(round((2+size/(size+1))*size))
-    y = int(round(4*size))
+    y = int(out["sysboxheight"] + out["borderthickness"])
     mb = bbox(m)
     sb = bbox(s)
     xoff = sb[0] - mb[0] + x
@@ -337,8 +342,9 @@ def lightning(size):
 
     # The lightning bolt motif.
 
-    # We always want this to be an even number of pixels in span.
-    width = round(7*size) * 2
+    # We always want this to be an even number of pixels in height,
+    # and an odd number in width.
+    width = round(7*size) * 2 - 1
     height = round(8*size) * 2
 
     # The outer edge of each side of the bolt goes to this point.
@@ -350,19 +356,19 @@ def lightning(size):
     innerx = round(7*size)
 
     for y in range(int(height)):
-       list = []
-       if y <= outery:
-           list.append(width-1-int(outerx * float(y) / outery + 0.3))
-       if y <= innery:
-           list.append(width-1-int(innerx * float(y) / innery + 0.3))
-       y0 = height-1-y
-       if y0 <= outery:
-           list.append(int(outerx * float(y0) / outery + 0.3))
-       if y0 <= innery:
-           list.append(int(innerx * float(y0) / innery + 0.3))
-       list.sort()
-       for x in range(int(list[0]), int(list[-1]+1)):
-           pixel(x, y, cY, canvas)
+        list = []
+        if y <= outery:
+            list.append(width-1-int(outerx * float(y) / outery + 0.3))
+        if y <= innery:
+            list.append(width-1-int(innerx * float(y) / innery + 0.3))
+        y0 = height-1-y
+        if y0 <= outery:
+            list.append(int(outerx * float(y0) / outery + 0.3))
+        if y0 <= innery:
+            list.append(int(innerx * float(y0) / innery + 0.3))
+        list.sort()
+        for x in range(int(list[0]), int(list[-1]+1)):
+            pixel(x, y, cY, canvas)
 
     # And draw a border.
     border(canvas, size, [(int(width-1),0,TR), (0,int(height-1),BL)])
@@ -386,37 +392,37 @@ def document(size):
 
     # Start by drawing a big white rectangle.
     for y in range(int(height)):
-       for x in range(int(width)):
-           pixel(x, y, cW, canvas)
+        for x in range(int(width)):
+            pixel(x, y, cW, canvas)
 
     # Now draw lines of text.
     for line in range(nlines):
-       # Decide where this line of text begins.
-       if line == 0:
-           start = round(4*size)
-       elif line < 5*nlines/7:
-           start = round((line - (nlines/7)) * size)
-       else:
-           start = round(1*size)
-       if start < round(1*size):
-           start = round(1*size)
-       # Decide where it ends.
-       endpoints = [10, 8, 11, 6, 5, 7, 5]
-       ey = line * 6.0 / (nlines-1)
-       eyf = math.floor(ey)
-       eyc = math.ceil(ey)
-       exf = endpoints[int(eyf)]
-       exc = endpoints[int(eyc)]
-       if eyf == eyc:
-           end = exf
-       else:
-           end = exf * (eyc-ey) + exc * (ey-eyf)
-       end = round(end * size)
-
-       liney = height - (lineht+linespc) * (line+1)
-       for x in range(int(start), int(end)):
-           for y in range(int(lineht)):
-               pixel(x, y+liney, cK, canvas)
+        # Decide where this line of text begins.
+        if line == 0:
+            start = round(4*size)
+        elif line < 5*nlines/7:
+            start = round((line - (nlines/7)) * size)
+        else:
+            start = round(1*size)
+        if start < round(1*size):
+            start = round(1*size)
+        # Decide where it ends.
+        endpoints = [10, 8, 11, 6, 5, 7, 5]
+        ey = line * 6.0 / (nlines-1)
+        eyf = math.floor(ey)
+        eyc = math.ceil(ey)
+        exf = endpoints[int(eyf)]
+        exc = endpoints[int(eyc)]
+        if eyf == eyc:
+            end = exf
+        else:
+            end = exf * (eyc-ey) + exc * (ey-eyf)
+        end = round(end * size)
+
+        liney = height - (lineht+linespc) * (line+1)
+        for x in range(int(start), int(end)):
+            for y in range(int(lineht)):
+                pixel(x, y+liney, cK, canvas)
 
     # And draw a border.
     border(canvas, size, \
@@ -444,37 +450,37 @@ def hat(size):
     brimbotc = round(10*size/3)
 
     for x in range(int(width)):
-       xs = float(x) * (len(topa)-1) / (width-1)
-       xf = math.floor(xs)
-       xc = math.ceil(xs)
-       topf = topa[int(xf)]
-       topc = topa[int(xc)]
-       if xf == xc:
-           top = topf
-       else:
-           top = topf * (xc-xs) + topc * (xs-xf)
-       top = math.floor(top)
-       bot = round(botl + (botr-botl) * x/(width-1))
-
-       for y in range(int(top), int(bot)):
-           pixel(x, y, cK, canvas)
+        xs = float(x) * (len(topa)-1) / (width-1)
+        xf = math.floor(xs)
+        xc = math.ceil(xs)
+        topf = topa[int(xf)]
+        topc = topa[int(xc)]
+        if xf == xc:
+            top = topf
+        else:
+            top = topf * (xc-xs) + topc * (xs-xf)
+        top = math.floor(top)
+        bot = round(botl + (botr-botl) * x/(width-1))
+
+        for y in range(int(top), int(bot)):
+            pixel(x, y, cK, canvas)
 
     # Now draw the brim.
     for x in range(int(width)):
-       brimtop = brimtopc + brimm * x
-       brimbot = brimbotc + brimm * x
-       for y in range(int(math.floor(brimtop)), int(math.ceil(brimbot))):
-           tophere = max(min(brimtop - y, 1), 0)
-           bothere = max(min(brimbot - y, 1), 0)
-           grey = bothere - tophere
-           # Only draw brim pixels over pixels which are (a) part
-           # of the main hat, and (b) not right on its edge.
-           if canvas.has_key((x,y)) and \
-           canvas.has_key((x,y-1)) and \
-           canvas.has_key((x,y+1)) and \
-           canvas.has_key((x-1,y)) and \
-           canvas.has_key((x+1,y)):
-               pixel(x, y, greypix(grey), canvas)
+        brimtop = brimtopc + brimm * x
+        brimbot = brimbotc + brimm * x
+        for y in range(int(math.floor(brimtop)), int(math.ceil(brimbot))):
+            tophere = max(min(brimtop - y, 1), 0)
+            bothere = max(min(brimbot - y, 1), 0)
+            grey = bothere - tophere
+            # Only draw brim pixels over pixels which are (a) part
+            # of the main hat, and (b) not right on its edge.
+            if canvas.has_key((x,y)) and \
+            canvas.has_key((x,y-1)) and \
+            canvas.has_key((x,y+1)) and \
+            canvas.has_key((x-1,y)) and \
+            canvas.has_key((x+1,y)):
+                pixel(x, y, greypix(grey), canvas)
 
     return canvas
 
@@ -496,42 +502,42 @@ def key(size):
 
     # Ellipse for the key head, minus an off-centre circular hole.
     for y in range(int(keyheadh)):
-       dy = (y-(keyheadh-1)/2.0) / (keyheadh/2.0)
-       dyh = (y-(keyheadh-1)/2.0) / (keyholed/2.0)
-       for x in range(int(keyheadw)):
-           dx = (x-(keyheadw-1)/2.0) / (keyheadw/2.0)
-           dxh = (x-(keyheadw-1)/2.0-keyholeoff) / (keyholed/2.0)
-           if dy*dy+dx*dx <= 1 and dyh*dyh+dxh*dxh > 1:
-               pixel(x + keyshaftw, y, cy, canvas)
+        dy = (y-(keyheadh-1)/2.0) / (keyheadh/2.0)
+        dyh = (y-(keyheadh-1)/2.0) / (keyholed/2.0)
+        for x in range(int(keyheadw)):
+            dx = (x-(keyheadw-1)/2.0) / (keyheadw/2.0)
+            dxh = (x-(keyheadw-1)/2.0-keyholeoff) / (keyholed/2.0)
+            if dy*dy+dx*dx <= 1 and dyh*dyh+dxh*dxh > 1:
+                pixel(x + keyshaftw, y, cy, canvas)
 
     # Rectangle for the key shaft, extended at the bottom for the
     # key head detail.
     for x in range(int(keyshaftw)):
-       top = round((keyheadh - keyshafth) / 2)
-       bot = round((keyheadh + keyshafth) / 2)
-       xs = float(x) * (len(keyhead)-1) / round((len(keyhead)-1)*size)
-       xf = math.floor(xs)
-       xc = math.ceil(xs)
-       in_head = 0
-       if xc < len(keyhead):
-           in_head = 1
-           yf = keyhead[int(xf)]
-           yc = keyhead[int(xc)]
-           if xf == xc:
-               bot = yf
-           else:
-               bot = yf * (xc-xs) + yc * (xs-xf)
-       for y in range(int(top),int(bot)):
-           pixel(x, y, cy, canvas)
-           if in_head:
-               last = (x, y)
-       if x == 0:
-           squarepix.append((x, int(top), TL))
-       if x == 0:
-           squarepix.append(last + (BL,))
-       if last != None and not in_head:
-           squarepix.append(last + (BR,))
-           last = None
+        top = round((keyheadh - keyshafth) / 2)
+        bot = round((keyheadh + keyshafth) / 2)
+        xs = float(x) * (len(keyhead)-1) / round((len(keyhead)-1)*size)
+        xf = math.floor(xs)
+        xc = math.ceil(xs)
+        in_head = 0
+        if xc < len(keyhead):
+            in_head = 1
+            yf = keyhead[int(xf)]
+            yc = keyhead[int(xc)]
+            if xf == xc:
+                bot = yf
+            else:
+                bot = yf * (xc-xs) + yc * (xs-xf)
+        for y in range(int(top),int(bot)):
+            pixel(x, y, cy, canvas)
+            if in_head:
+                last = (x, y)
+        if x == 0:
+            squarepix.append((x, int(top), TL))
+        if x == 0:
+            squarepix.append(last + (BL,))
+        if last != None and not in_head:
+            squarepix.append(last + (BR,))
+            last = None
 
     # And draw a border.
     border(canvas, size, squarepix)
@@ -548,28 +554,28 @@ def linedist(x1,y1, x2,y2, x,y):
     # Special case: if x1,y1 and x2,y2 are the same point, we
     # don't attempt to extrapolate it into a line at all.
     if x1 != x2 or y1 != y2:
-       # First, find the nearest point to x,y on the infinite
-       # projection of the line segment. So we construct a vector
-       # n perpendicular to that segment...
-       nx = y2-y1
-       ny = x1-x2
-       # ... compute the dot product of (x1,y1)-(x,y) with that
-       # vector...
-       nd = (x1-x)*nx + (y1-y)*ny
-       # ... multiply by the vector we first thought of...
-       ndx = nd * nx
-       ndy = nd * ny
-       # ... and divide twice by the length of n.
-       ndx = ndx / (nx*nx+ny*ny)
-       ndy = ndy / (nx*nx+ny*ny)
-       # That gives us a displacement vector from x,y to the
-       # nearest point. See if it's within the range of the line
-       # segment.
-       cx = x + ndx
-       cy = y + ndy
-       if cx >= min(x1,x2) and cx <= max(x1,x2) and \
-       cy >= min(y1,y2) and cy <= max(y1,y2):
-           vectors.append((ndx,ndy))
+        # First, find the nearest point to x,y on the infinite
+        # projection of the line segment. So we construct a vector
+        # n perpendicular to that segment...
+        nx = y2-y1
+        ny = x1-x2
+        # ... compute the dot product of (x1,y1)-(x,y) with that
+        # vector...
+        nd = (x1-x)*nx + (y1-y)*ny
+        # ... multiply by the vector we first thought of...
+        ndx = nd * nx
+        ndy = nd * ny
+        # ... and divide twice by the length of n.
+        ndx = ndx / (nx*nx+ny*ny)
+        ndy = ndy / (nx*nx+ny*ny)
+        # That gives us a displacement vector from x,y to the
+        # nearest point. See if it's within the range of the line
+        # segment.
+        cx = x + ndx
+        cy = y + ndy
+        if cx >= min(x1,x2) and cx <= max(x1,x2) and \
+        cy >= min(y1,y2) and cy <= max(y1,y2):
+            vectors.append((ndx,ndy))
 
     # Now we have up to three candidate result vectors: (ndx,ndy)
     # as computed just above, and the two vectors to the ends of
@@ -578,10 +584,10 @@ def linedist(x1,y1, x2,y2, x,y):
     vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)]
     bestlen, best = None, None
     for v in vectors:
-       vlen = v[0]*v[0]+v[1]*v[1]
-       if bestlen == None or bestlen > vlen:
-           bestlen = vlen
-           best = v
+        vlen = v[0]*v[0]+v[1]*v[1]
+        if bestlen == None or bestlen > vlen:
+            bestlen = vlen
+            best = v
     return best
 
 def spanner(size):
@@ -612,66 +618,136 @@ def spanner(size):
     ]
 
     for y in range(int(cmax)):
-       for x in range(int(cmax)):
-           vectors = [linedist(a,b,c,d,x,y) for ((a,b),(c,d)) in segments]
-           dists = [memoisedsqrt(vx*vx+vy*vy) for (vx,vy) in vectors]
-
-           # If the distance to the hole line is less than
-           # holeradius, we're not part of the spanner.
-           if dists[0] < holeradius:
-               continue
-           # If the distance to the head `line' is less than
-           # headradius, we are part of the spanner; likewise if
-           # the distance to the shaft line is less than
-           # shaftwidth _and_ the resulting shaft point isn't
-           # beyond the shaft end.
-           if dists[1] > headradius and \
-           (dists[2] > shaftwidth or x+vectors[2][0] >= shaftend):
-               continue
-
-           # We're part of the spanner. Now compute the highlight
-           # on this pixel. We do this by computing a `slope
-           # vector', which points from this pixel in the
-           # direction of its nearest edge. We store an array of
-           # slope vectors, in polar coordinates.
-           angles = [math.atan2(vy,vx) for (vx,vy) in vectors]
-           slopes = []
-           if dists[0] < holeradius + holehighlight:
-               slopes.append(((dists[0]-holeradius)/holehighlight,angles[0]))
-           if dists[1]/headradius < dists[2]/shaftwidth:
-               if dists[1] > headradius - headhighlight and dists[1] < headradius:
-                   slopes.append(((headradius-dists[1])/headhighlight,math.pi+angles[1]))
-           else:
-               if dists[2] > shaftwidth - shafthighlight and dists[2] < shaftwidth:
-                   slopes.append(((shaftwidth-dists[2])/shafthighlight,math.pi+angles[2]))
-           # Now we find the smallest distance in that array, if
-           # any, and that gives us a notional position on a
-           # sphere which we can use to compute the final
-           # highlight level.
-           bestdist = None
-           bestangle = 0
-           for dist, angle in slopes:
-               if bestdist == None or bestdist > dist:
-                   bestdist = dist
-                   bestangle = angle
-           if bestdist == None:
-               bestdist = 1.0
-           sx = (1.0-bestdist) * math.cos(bestangle)
-           sy = (1.0-bestdist) * math.sin(bestangle)
-           sz = math.sqrt(1.0 - sx*sx - sy*sy)
-           shade = sx-sy+sz / math.sqrt(3) # can range from -1 to +1
-           shade = 1.0 - (1-shade)/3
-
-           pixel(x, y, yellowpix(shade), canvas)
+        for x in range(int(cmax)):
+            vectors = [linedist(a,b,c,d,x,y) for ((a,b),(c,d)) in segments]
+            dists = [memoisedsqrt(vx*vx+vy*vy) for (vx,vy) in vectors]
+
+            # If the distance to the hole line is less than
+            # holeradius, we're not part of the spanner.
+            if dists[0] < holeradius:
+                continue
+            # If the distance to the head `line' is less than
+            # headradius, we are part of the spanner; likewise if
+            # the distance to the shaft line is less than
+            # shaftwidth _and_ the resulting shaft point isn't
+            # beyond the shaft end.
+            if dists[1] > headradius and \
+            (dists[2] > shaftwidth or x+vectors[2][0] >= shaftend):
+                continue
+
+            # We're part of the spanner. Now compute the highlight
+            # on this pixel. We do this by computing a `slope
+            # vector', which points from this pixel in the
+            # direction of its nearest edge. We store an array of
+            # slope vectors, in polar coordinates.
+            angles = [math.atan2(vy,vx) for (vx,vy) in vectors]
+            slopes = []
+            if dists[0] < holeradius + holehighlight:
+                slopes.append(((dists[0]-holeradius)/holehighlight,angles[0]))
+            if dists[1]/headradius < dists[2]/shaftwidth:
+                if dists[1] > headradius - headhighlight and dists[1] < headradius:
+                    slopes.append(((headradius-dists[1])/headhighlight,math.pi+angles[1]))
+            else:
+                if dists[2] > shaftwidth - shafthighlight and dists[2] < shaftwidth:
+                    slopes.append(((shaftwidth-dists[2])/shafthighlight,math.pi+angles[2]))
+            # Now we find the smallest distance in that array, if
+            # any, and that gives us a notional position on a
+            # sphere which we can use to compute the final
+            # highlight level.
+            bestdist = None
+            bestangle = 0
+            for dist, angle in slopes:
+                if bestdist == None or bestdist > dist:
+                    bestdist = dist
+                    bestangle = angle
+            if bestdist == None:
+                bestdist = 1.0
+            sx = (1.0-bestdist) * math.cos(bestangle)
+            sy = (1.0-bestdist) * math.sin(bestangle)
+            sz = math.sqrt(1.0 - sx*sx - sy*sy)
+            shade = sx-sy+sz / math.sqrt(3) # can range from -1 to +1
+            shade = 1.0 - (1-shade)/3
+
+            pixel(x, y, yellowpix(shade), canvas)
 
     # And draw a border.
     border(canvas, size, [])
 
     return canvas
 
+def box(size, back):
+    canvas = {}
+
+    # The back side of the cardboard box in the installer icon.
+
+    boxwidth = round(15 * size)
+    boxheight = round(12 * size)
+    boxdepth = round(4 * size)
+    boxfrontflapheight = round(5 * size)
+    boxrightflapheight = round(3 * size)
+
+    # Three shades of basically acceptable brown, all achieved by
+    # halftoning between two of the Windows-16 colours. I'm quite
+    # pleased that was feasible at all!
+    dark = halftone(cr, cK)
+    med = halftone(cr, cy)
+    light = halftone(cr, cY)
+    # We define our halftoning parity in such a way that the black
+    # pixels along the RHS of the visible part of the box back
+    # match up with the one-pixel black outline around the
+    # right-hand side of the box. In other words, we want the pixel
+    # at (-1, boxwidth-1) to be black, and hence the one at (0,
+    # boxwidth) too.
+    parityadjust = int(boxwidth) % 2
+
+    # The entire back of the box.
+    if back:
+        for x in range(int(boxwidth + boxdepth)):
+            ytop = max(-x-1, -boxdepth-1)
+            ybot = min(boxheight, boxheight+boxwidth-1-x)
+            for y in range(int(ytop), int(ybot)):
+                pixel(x, y, dark[(x+y+parityadjust) % 2], canvas)
+
+    # Even when drawing the back of the box, we still draw the
+    # whole shape, because that means we get the right overall size
+    # (the flaps make the box front larger than the box back) and
+    # it'll all be overwritten anyway.
+
+    # The front face of the box.
+    for x in range(int(boxwidth)):
+        for y in range(int(boxheight)):
+            pixel(x, y, med[(x+y+parityadjust) % 2], canvas)
+    # The right face of the box.
+    for x in range(int(boxwidth), int(boxwidth+boxdepth)):
+        ybot = boxheight + boxwidth-x
+        ytop = ybot - boxheight
+        for y in range(int(ytop), int(ybot)):
+            pixel(x, y, dark[(x+y+parityadjust) % 2], canvas)
+    # The front flap of the box.
+    for y in range(int(boxfrontflapheight)):
+        xadj = int(round(-0.5*y))
+        for x in range(int(xadj), int(xadj+boxwidth)):
+            pixel(x, y, light[(x+y+parityadjust) % 2], canvas)
+    # The right flap of the box.
+    for x in range(int(boxwidth), int(boxwidth + boxdepth + boxrightflapheight + 1)):
+        ytop = max(boxwidth - 1 - x, x - boxwidth - 2*boxdepth - 1)
+        ybot = min(x - boxwidth - 1, boxwidth + 2*boxrightflapheight - 1 - x)
+        for y in range(int(ytop), int(ybot+1)):
+            pixel(x, y, med[(x+y+parityadjust) % 2], canvas)
+
+    # And draw a border.
+    border(canvas, size, [(0, int(boxheight)-1, BL)])
+
+    return canvas
+
+def boxback(size):
+    return box(size, 1)
+def boxfront(size):
+    return box(size, 0)
+
 # Functions to draw entire icons by composing the above components.
 
-def xybolt(c1, c2, size, boltoffx=0, boltoffy=0):
+def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, aux={}):
     # Two unspecified objects and a lightning bolt.
 
     canvas = {}
@@ -683,10 +759,12 @@ def xybolt(c1, c2, size, boltoffx=0, boltoffy=0):
     bb = bbox(c2)
     assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
     overlay(c2, w-bb[2], 0-bb[1], canvas)
+    aux["c2pos"] = (w-bb[2], 0-bb[1])
     # Position c1 against the bottom left of the icon.
     bb = bbox(c1)
     assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
     overlay(c1, 0-bb[0], h-bb[3], canvas)
+    aux["c1pos"] = (0-bb[0], h-bb[3])
     # Place the lightning bolt artistically off-centre. (The
     # rationale for this positioning is that it's centred on the
     # midpoint between the centres of the two monitors in the PuTTY
@@ -694,8 +772,8 @@ def xybolt(c1, c2, size, boltoffx=0, boltoffy=0):
     # calculation here on that.)
     bb = bbox(bolt)
     assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
-    overlay(bolt, (w-bb[0]-bb[2])/2 - round((1-boltoffx)*size), \
-    (h-bb[1]-bb[3])/2 - round((2-boltoffy)*size), canvas)
+    overlay(bolt, (w-bb[0]-bb[2])/2 + round(boltoffx*size), \
+    (h-bb[1]-bb[3])/2 + round((boltoffy-2)*size), canvas)
 
     return canvas
 
@@ -715,7 +793,19 @@ def puttygen_icon(size):
     return xybolt(computer(size), key(size), size, boltoffx=2)
 
 def pscp_icon(size):
-    return xybolt(document(size), computer(size), size, boltoffx=1)
+    return xybolt(document(size), computer(size), size)
+
+def puttyins_icon(size):
+    aret = {}
+    # The box back goes behind the lightning bolt.
+    canvas = xybolt(boxback(size), computer(size), size, boltoffx=-2, boltoffy=+1, aux=aret)
+    # But the box front goes over the top, so that the lightning
+    # bolt appears to come _out_ of the box. Here it's useful to
+    # know the exact coordinates where xybolt placed the box back,
+    # so we can overlay the box front exactly on top of it.
+    c1x, c1y = aret["c1pos"]
+    overlay(boxfront(size), c1x, c1y, canvas)
+    return canvas
 
 def pterm_icon(size):
     # Just a really big computer.
@@ -747,7 +837,7 @@ def pageant_icon(size):
     canvas = {}
     w = h = round(32 * size)
 
-    c = computer(size * 1.3)
+    c = computer(size * 1.2)
     ht = hat(size)
 
     cbb = bbox(c)
@@ -766,13 +856,13 @@ def pageant_icon(size):
     hty = topy(ht)
     yrelmin = None
     for cx in cty.keys():
-       hx = cx - xrel
-       assert hty.has_key(hx)
-       yrel = cty[cx] - hty[hx]
-       if yrelmin == None:
-           yrelmin = yrel
-       else:
-           yrelmin = min(yrelmin, yrel)
+        hx = cx - xrel
+        assert hty.has_key(hx)
+        yrel = cty[cx] - hty[hx]
+        if yrelmin == None:
+            yrelmin = yrel
+        else:
+            yrelmin = min(yrelmin, yrel)
 
     # Overlay the hat on the computer.
     overlay(ht, xrel, yrelmin, c)
@@ -792,27 +882,27 @@ import sys
 def testrun(func, fname):
     canvases = []
     for size in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]:
-       canvases.append(func(size))
+        canvases.append(func(size))
     wid = 0
     ht = 0
     for canvas in canvases:
-       minx, miny, maxx, maxy = bbox(canvas)
-       wid = max(wid, maxx-minx+4)
-       ht = ht + maxy-miny+4
+        minx, miny, maxx, maxy = bbox(canvas)
+        wid = max(wid, maxx-minx+4)
+        ht = ht + maxy-miny+4
     block = []
     for canvas in canvases:
-       minx, miny, maxx, maxy = bbox(canvas)
-       block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2))
+        minx, miny, maxx, maxy = bbox(canvas)
+        block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2))
     p = os.popen("convert -depth 8 -size %dx%d rgb:- %s" % (wid,ht,fname), "w")
     assert len(block) == ht
     for line in block:
-       assert len(line) == wid
-       for r, g, b, a in line:
-           # Composite on to orange.
-           r = int(round((r * a + 255 * (255-a)) / 255.0))
-           g = int(round((g * a + 128 * (255-a)) / 255.0))
-           b = int(round((b * a +   0 * (255-a)) / 255.0))
-           p.write("%c%c%c" % (r,g,b))
+        assert len(line) == wid
+        for r, g, b, a in line:
+            # Composite on to orange.
+            r = int(round((r * a + 255 * (255-a)) / 255.0))
+            g = int(round((g * a + 128 * (255-a)) / 255.0))
+            b = int(round((b * a +   0 * (255-a)) / 255.0))
+            p.write("%c%c%c" % (r,g,b))
     p.close()
 
 def drawicon(func, width, fname, orangebackground = 0):
@@ -825,15 +915,15 @@ def drawicon(func, width, fname, orangebackground = 0):
     p = os.popen("convert -depth 8 -size %dx%d rgba:- %s" % (width,width,fname), "w")
     assert len(block) == width
     for line in block:
-       assert len(line) == width
-       for r, g, b, a in line:
-           if orangebackground:
-               # Composite on to orange.
-               r = int(round((r * a + 255 * (255-a)) / 255.0))
-               g = int(round((g * a + 128 * (255-a)) / 255.0))
-               b = int(round((b * a +   0 * (255-a)) / 255.0))
-               a = 255
-           p.write("%c%c%c%c" % (r,g,b,a))
+        assert len(line) == width
+        for r, g, b, a in line:
+            if orangebackground:
+                # Composite on to orange.
+                r = int(round((r * a + 255 * (255-a)) / 255.0))
+                g = int(round((g * a + 128 * (255-a)) / 255.0))
+                b = int(round((b * a +   0 * (255-a)) / 255.0))
+                a = 255
+            p.write("%c%c%c%c" % (r,g,b,a))
     p.close()
 
 args = sys.argv[1:]
@@ -845,21 +935,21 @@ doingargs = 1
 realargs = []
 for arg in args:
     if doingargs and arg[0] == "-":
-       if arg == "-t":
-           test = 1
-       elif arg == "-it":
-           orangebackground = 1
-       elif arg == "-2":
-           colours = 0
-       elif arg == "-T":
-           colours = 2
-       elif arg == "--":
-           doingargs = 0
-       else:
-           sys.stderr.write("unrecognised option '%s'\n" % arg)
-           sys.exit(1)
+        if arg == "-t":
+            test = 1
+        elif arg == "-it":
+            orangebackground = 1
+        elif arg == "-2":
+            colours = 0
+        elif arg == "-T":
+            colours = 2
+        elif arg == "--":
+            doingargs = 0
+        else:
+            sys.stderr.write("unrecognised option '%s'\n" % arg)
+            sys.exit(1)
     else:
-       realargs.append(arg)
+        realargs.append(arg)
 
 if colours == 0:
     # Monochrome.
@@ -867,47 +957,49 @@ if colours == 0:
     cY=cy=cW = 1
     cT = -1
     def greypix(value):
-       return [cK,cW][int(round(value))]
+        return [cK,cW][int(round(value))]
     def yellowpix(value):
-       return [cK,cW][int(round(value))]
+        return [cK,cW][int(round(value))]
     def bluepix(value):
-       return cK
+        return cK
     def dark(value):
-       return [cT,cK][int(round(value))]
+        return [cT,cK][int(round(value))]
     def blend(col1, col2):
-       if col1 == cT:
-           return col2
-       else:
-           return col1
+        if col1 == cT:
+            return col2
+        else:
+            return col1
     pixvals = [
     (0x00, 0x00, 0x00, 0xFF), # cK
     (0xFF, 0xFF, 0xFF, 0xFF), # cW
     (0x00, 0x00, 0x00, 0x00), # cT
     ]
     def outpix(colour):
-       return pixvals[colour]
+        return pixvals[colour]
     def finalisepix(colour):
-       return colour
+        return colour
+    def halftone(col1, col2):
+        return (col1, col2)
 elif colours == 1:
     # Windows 16-colour palette.
     cK,cr,cg,cy,cb,cm,cc,cP,cw,cR,cG,cY,cB,cM,cC,cW = range(16)
     cT = -1
     cD = -2 # special translucent half-darkening value used internally
     def greypix(value):
-       return [cK,cw,cw,cP,cW][int(round(4*value))]
+        return [cK,cw,cw,cP,cW][int(round(4*value))]
     def yellowpix(value):
-       return [cK,cy,cY][int(round(2*value))]
+        return [cK,cy,cY][int(round(2*value))]
     def bluepix(value):
-       return [cK,cb,cB][int(round(2*value))]
+        return [cK,cb,cB][int(round(2*value))]
     def dark(value):
-       return [cT,cD,cK][int(round(2*value))]
+        return [cT,cD,cK][int(round(2*value))]
     def blend(col1, col2):
-       if col1 == cT:
-           return col2
-       elif col1 == cD:
-           return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2]
-       else:
-           return col1
+        if col1 == cT:
+            return col2
+        elif col1 == cD:
+            return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2]
+        else:
+            return col1
     pixvals = [
     (0x00, 0x00, 0x00, 0xFF), # cK
     (0x80, 0x00, 0x00, 0xFF), # cr
@@ -929,12 +1021,14 @@ elif colours == 1:
     (0x00, 0x00, 0x00, 0x00), # cT
     ]
     def outpix(colour):
-       return pixvals[colour]
+        return pixvals[colour]
     def finalisepix(colour):
-       # cD is used internally, but can't be output. Convert to cK.
-       if colour == cD:
-           return cK
-       return colour
+        # cD is used internally, but can't be output. Convert to cK.
+        if colour == cD:
+            return cK
+        return colour
+    def halftone(col1, col2):
+        return (col1, col2)
 else:
     # True colour.
     cK = (0x00, 0x00, 0x00, 0xFF)
@@ -956,37 +1050,42 @@ else:
     cD = (0x00, 0x00, 0x00, 0x80)
     cT = (0x00, 0x00, 0x00, 0x00)
     def greypix(value):
-       value = max(min(value, 1), 0)
-       return (int(round(0xFF*value)),) * 3 + (0xFF,)
+        value = max(min(value, 1), 0)
+        return (int(round(0xFF*value)),) * 3 + (0xFF,)
     def yellowpix(value):
-       value = max(min(value, 1), 0)
-       return (int(round(0xFF*value)),) * 2 + (0, 0xFF)
+        value = max(min(value, 1), 0)
+        return (int(round(0xFF*value)),) * 2 + (0, 0xFF)
     def bluepix(value):
-       value = max(min(value, 1), 0)
-       return (0, 0, int(round(0xFF*value)), 0xFF)
+        value = max(min(value, 1), 0)
+        return (0, 0, int(round(0xFF*value)), 0xFF)
     def dark(value):
-       value = max(min(value, 1), 0)
-       return (0, 0, 0, int(round(0xFF*value)))
+        value = max(min(value, 1), 0)
+        return (0, 0, 0, int(round(0xFF*value)))
     def blend(col1, col2):
-       r1,g1,b1,a1 = col1
-       r2,g2,b2,a2 = col2
-       r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0))
-       g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0))
-       b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0))
-       a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0))
-       return r, g, b, a
+        r1,g1,b1,a1 = col1
+        r2,g2,b2,a2 = col2
+        r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0))
+        g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0))
+        b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0))
+        a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0))
+        return r, g, b, a
     def outpix(colour):
-       return colour
+        return colour
     if colours == 2:
-       # True colour with no alpha blending: we still have to
-       # finalise half-dark pixels to black.
-       def finalisepix(colour):
-           if colour[3] > 0:
-               return colour[:3] + (0xFF,)
-           return colour
+        # True colour with no alpha blending: we still have to
+        # finalise half-dark pixels to black.
+        def finalisepix(colour):
+            if colour[3] > 0:
+                return colour[:3] + (0xFF,)
+            return colour
     else:
-       def finalisepix(colour):
-           return colour
+        def finalisepix(colour):
+            return colour
+    def halftone(col1, col2):
+        r1,g1,b1,a1 = col1
+        r2,g2,b2,a2 = col2
+        colret = (int(r1+r2)/2, int(g1+g2)/2, int(b1+b2)/2, int(a1+a2)/2)
+        return (colret, colret)
 
 if test:
     testrun(eval(realargs[0]), realargs[1])