From: Simon Tatham Date: Wed, 18 Dec 2013 21:32:17 +0000 (+0000) Subject: Small diff against bedstead.c, plus Python/Tk user interface. X-Git-Tag: bedstead-001.000~20 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~bjharris/git?a=commitdiff_plain;h=7f1d1a27172b0beabf5a2e628f4b1f1694880548;p=bedstead.git Small diff against bedstead.c, plus Python/Tk user interface. --- diff --git a/bedstead.c b/bedstead.c index cc5a676..93aba31 100644 --- a/bedstead.c +++ b/bedstead.c @@ -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 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("", click) +cont.canvas.bind("", drag) +tkroot.bind("", key) +cont.canvas.pack() + +mainloop()