chiark / gitweb /
Small diff against bedstead.c, plus Python/Tk user interface.
authorSimon Tatham <anakin@pobox.com>
Wed, 18 Dec 2013 21:32:17 +0000 (21:32 +0000)
committerBen Harris <bjh21@bjh21.me.uk>
Wed, 18 Dec 2013 21:32:17 +0000 (21:32 +0000)
bedstead.c
editor.py [new file with mode: 0644]

index cc5a67632724216a5e6131329129a52dbbd49f93..93aba3141f8ad99d630e9248a393fdfadad27f7e 100644 (file)
@@ -1048,6 +1048,32 @@ main(int argc, char **argv)
        int const nglyphs = sizeof(glyphs) / sizeof(glyphs[0]);
        int extraglyphs = 0;
 
+        if (argc > 1) {
+                char data[YSIZE], *endptr;
+                int i, y;
+                unsigned long u;
+
+                for (y = 0; y < YSIZE; y++)
+                        data[y] = 0;
+
+                y = 0;
+                for (i = 1; i < argc; i++) {
+                        if (y >= YSIZE) {
+                                fprintf(stderr, "too many arguments\n");
+                                return 1;
+                        }
+                        u = strtoul(argv[i], &endptr, 0);
+                        if (u > 077 || !argv[i] || *endptr) {
+                                fprintf(stderr, "invalid argument \"%s\"\n",
+                                        argv[i]);
+                                return 1;
+                        }
+                        data[y++] = u;
+                }
+               dochar(data, 0);
+                return 0;
+        }
+
        for (i = 0; i < nglyphs; i++)
                if (glyphs[i].unicode == -1)
                        extraglyphs++;
diff --git a/editor.py b/editor.py
new file mode 100644 (file)
index 0000000..1b8f0c1
--- /dev/null
+++ b/editor.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+
+import sys
+import string
+
+from Tkinter import *
+
+import subprocess
+
+tkroot = Tk()
+
+class Container:
+    pass
+
+cont = Container()
+
+gutter = 20
+pixel = 32
+XSIZE, YSIZE = 5, 9
+LEFT, TOP = 100, 700 # for transforming coordinates returned from bedstead
+
+cont.canvas = Canvas(tkroot,
+                     width=2 * (XSIZE*pixel) + 3*gutter,
+                     height=YSIZE*pixel + 2*gutter,
+                     bg='white')
+cont.bitmap = [0] * YSIZE
+cont.oldbitmap = cont.bitmap[:]
+cont.pixels = [[None]*XSIZE for y in range(YSIZE)]
+cont.polygons = []
+
+for x in range(XSIZE+1):
+    cont.canvas.create_line(gutter + x*pixel, gutter,
+                            gutter + x*pixel, gutter + YSIZE*pixel)
+for y in range(YSIZE+1):
+    cont.canvas.create_line(gutter, gutter + y*pixel,
+                            gutter + XSIZE*pixel, gutter + y*pixel)
+
+dragging = None
+
+def getpixel(x, y):
+    assert x >= 0 and x < XSIZE and y >= 0 and y < YSIZE
+    bit = 1 << (XSIZE-1 - x)
+    return cont.bitmap[y] & bit
+
+def setpixel(x, y, state):
+    assert x >= 0 and x < XSIZE and y >= 0 and y < YSIZE
+    bit = 1 << (XSIZE-1 - x)
+    if state and not (cont.bitmap[y] & bit):
+        cont.bitmap[y] |= bit
+        cont.pixels[y][x] = cont.canvas.create_rectangle(
+            gutter + x*pixel, gutter + y*pixel,
+            gutter + (x+1)*pixel, gutter + (y+1)*pixel,
+            fill='black')
+    elif not state and (cont.bitmap[y] & bit):
+        cont.bitmap[y] &= ~bit
+        cont.canvas.delete(cont.pixels[y][x])
+        cont.pixels[y][x] = None
+
+def regenerate():
+    if cont.oldbitmap == cont.bitmap:
+        return
+
+    cont.oldbitmap = cont.bitmap[:]
+
+    for pg in cont.polygons:
+        cont.canvas.delete(pg)
+    cont.polygons = []
+
+    data = subprocess.check_output(["./bedstead"] + map(str, cont.bitmap))
+    paths = []
+    path = None
+    for line in data.splitlines():
+        words = line.split()
+        if len(words) >= 3 and words[2] in ["m","l"]:
+            x = int((float(words[0])-LEFT)*pixel*0.01 + 2*gutter + XSIZE*pixel)
+            y = int((TOP - float(words[1]))*pixel*0.01 + gutter) 
+            if words[2] == "m":
+                path = []
+                paths.append(path)
+            path.append([x,y])
+
+    # The output from 'bedstead' will be a set of disjoint paths,
+    # in the Postscript style (going one way around the outside of
+    # filled areas, and the other way around internal holes in
+    # those areas). Python/Tk doesn't know how to fill an
+    # arbitrary path in that representation, so instead we must
+    # convert into a set of individual Tk polygons (convex shapes
+    # with a single closed outline) and display them in the right
+    # order with the right colour.
+    #
+    # A neat way to arrange this is to compute the area enclosed
+    # by each polygon, essentially by integration: for each line
+    # segment (x0,y0)-(x1,y1), sum the y difference (y1-y0) times
+    # the average x value, which gives the area between that line
+    # segment and the corresponding segment of the x-axis. After
+    # we go all the way round an outline in this way, we'll have
+    # precisely the area enclosed by the outline, no matter how
+    # many times it doubles back on itself (because every piece of
+    # x-axis has been cancelled out by an outline going back the
+    # other way). Furthermore, the sign of the integral we've
+    # computed tells us whether the outline goes one way or the
+    # other around the area.
+    #
+    # So then we sort our paths into descending order of the
+    # absolute value of its computed area (guaranteeing that any
+    # path contained inside another appears after it, since it
+    # must enclose a strictly smaller area) and fill each one with
+    # a colour based on the area's sign.
+    #
+    # This strategy depends critically on 'bedstead' having given
+    # us sensible paths in the first place: it wouldn't handle an
+    # _arbitrary_ PostScript path, with loops allowed to overlap
+    # and intersect rather than being neatly nested.
+    pathswithmetadata = []
+    for path in paths:
+        area = 0
+        for i in range(len(path)):
+            x0, y0 = path[i-1]
+            x1, y1 = path[i]
+            area += (y1-y0) * (x0+x1)/2
+        pathswithmetadata.append([abs(area),
+                                 ('black' if area>0 else 'white'),
+                                  path])
+    pathswithmetadata.sort(reverse=True)
+
+    for _, colour, path in pathswithmetadata:
+        if len(path) > 1 and path[0] == path[-1]:
+            del path[-1]
+            args = sum(path, []) # x,y,x,y,...,x.y
+            pg = cont.canvas.create_polygon(*args, fill=colour)
+            cont.polygons.append(pg)
+
+def click(event):
+    for dragstartx in gutter, 2*gutter + XSIZE*pixel:
+        x = (event.x - dragstartx) / pixel
+        y = (event.y - gutter) / pixel
+        if x >= 0 and x < XSIZE and y >= 0 and y < YSIZE:
+            cont.dragstartx = dragstartx
+            cont.dragstate = not getpixel(x,y)
+            setpixel(x, y, cont.dragstate)
+            regenerate()
+            break
+
+def drag(event):
+    x = (event.x - cont.dragstartx) / pixel
+    y = (event.y - gutter) / pixel
+    if x >= 0 and x < XSIZE and y >= 0 and y < YSIZE:
+        setpixel(x, y, cont.dragstate)
+        regenerate()
+        return
+
+def key(event):
+    if event.char in (' '):
+        bm = ",".join(map(lambda n: "%03o" % n, cont.bitmap))
+        print " {{%s}, 0x }," % bm
+    elif event.char in ('c','C'):
+        for y in range(YSIZE):
+            for x in range(XSIZE):
+                setpixel(x, y, 0)
+        regenerate()
+    elif event.char in ('q','Q','\x11'):
+        sys.exit(0)
+
+cont.canvas.bind("<Button-1>", click)
+cont.canvas.bind("<B1-Motion>", drag)
+tkroot.bind("<Key>", key)
+cont.canvas.pack()
+
+mainloop()