mirror of
https://github.com/phooky/Apple-410.git
synced 2024-06-12 14:29:28 +00:00
Compare commits
34 Commits
plot_to_sv
...
default
Author | SHA1 | Date | |
---|---|---|---|
|
1b05e9edb5 | ||
|
67ef6ce139 | ||
|
e423b7d1a0 | ||
|
3aa063ef79 | ||
|
308c88f67e | ||
|
b0d6654960 | ||
|
408ae97580 | ||
|
c6d3b86b95 | ||
|
8f1d360a44 | ||
|
0e13833b85 | ||
|
cda0694f04 | ||
|
bae6443d5b | ||
|
4872944fd3 | ||
|
7268a1e872 | ||
|
b089612d92 | ||
|
af2679bab4 | ||
|
8525bee4aa | ||
|
2b35d39691 | ||
|
2e5109bdbf | ||
|
51cc15f152 | ||
|
81e3a77b36 | ||
|
6455077236 | ||
|
1a9c639ca8 | ||
|
207763984f | ||
|
f603dc0788 | ||
|
be764807bc | ||
|
5f8aa51566 | ||
|
b97a1b69d1 | ||
|
2a92ffd36b | ||
|
b98008f113 | ||
|
c87b0b24e3 | ||
|
5ff95991ea | ||
|
3dfef88012 | ||
|
53ca795504 |
|
@ -1,13 +1,6 @@
|
|||
#!/usr/bin/python3
|
||||
import serial
|
||||
import time
|
||||
from .plot_to_svg import Plotter
|
||||
|
||||
def plot_to_svg(instream, outstream):
|
||||
"Convert a set of plot instructions to an SVG. Works mininmally."
|
||||
p = Plotter()
|
||||
p.read(instream)
|
||||
p.write(outstream)
|
||||
|
||||
class Apple410Common:
|
||||
def __init__(self):
|
||||
|
|
|
@ -47,7 +47,7 @@ def svg2plot():
|
|||
f = open(args.SVG)
|
||||
plot_svg(f,center=args.center)
|
||||
|
||||
from .plot_to_svg import Plotter
|
||||
from .plot_to_cairo import CairoPlotter
|
||||
|
||||
def plot2svg():
|
||||
parser = argparse.ArgumentParser("plot2svg",
|
||||
|
@ -58,7 +58,21 @@ def plot2svg():
|
|||
f = sys.stdin
|
||||
else:
|
||||
f = open(args.PLOT)
|
||||
p = Plotter()
|
||||
p = CairoPlotter(sys.stdout.buffer)
|
||||
p.read(f)
|
||||
p.write(sys.stdout)
|
||||
p.write()
|
||||
|
||||
def plot2png():
|
||||
parser = argparse.ArgumentParser("plot2png",
|
||||
description="Convert a set of Apple 410 Color Plotter commands to a PNG")
|
||||
parser.add_argument('PLOT', help='The plotter file to process. "-" will read plotter commands from standard input.')
|
||||
args = parser.parse_args()
|
||||
if args.PLOT == '-':
|
||||
f = sys.stdin
|
||||
else:
|
||||
f = open(args.PLOT)
|
||||
p = CairoPlotter(sys.stdout.buffer,'PNG')
|
||||
p.read(f)
|
||||
p.write()
|
||||
|
||||
|
||||
|
|
BIN
apple410/data/a410_chars.pickle
Normal file
BIN
apple410/data/a410_chars.pickle
Normal file
Binary file not shown.
BIN
apple410/data/a410_points.pickle
Normal file
BIN
apple410/data/a410_points.pickle
Normal file
Binary file not shown.
|
@ -2,6 +2,8 @@
|
|||
import sys
|
||||
import math
|
||||
import cairo
|
||||
import pkg_resources
|
||||
import pickle
|
||||
|
||||
def coordlist(line,expected=-1):
|
||||
l = list(map(float,line.split(',')))
|
||||
|
@ -20,11 +22,64 @@ W = 2394
|
|||
H = 1759
|
||||
LW = 5
|
||||
|
||||
class Font:
|
||||
def __init__(self,path):
|
||||
f = open(pkg_resources.resource_filename('apple410',path),'rb')
|
||||
self.char_map = pickle.load(f)
|
||||
|
||||
|
||||
def unpack_byte(b):
|
||||
"convert two 4-bit signed packed numbers to a tuple"
|
||||
# this packing is... unusual.
|
||||
x = b >> 4
|
||||
y = b % 16
|
||||
if y > 8: # now the weird
|
||||
x -= 1
|
||||
y -= 16
|
||||
return (x,y)
|
||||
|
||||
def render_char(self, ctx, char, xoff, yoff):
|
||||
d = list(self.char_map[char])
|
||||
while d:
|
||||
cmd = d.pop(0)
|
||||
cn, ca = cmd >> 4, cmd % 16
|
||||
for _ in range(ca):
|
||||
(x1, y1) = Font.unpack_byte(d.pop(0))
|
||||
x = x1 + xoff
|
||||
y = y1 + yoff
|
||||
if cn == 0:
|
||||
ctx.move_to(x,y)
|
||||
elif cn == 2:
|
||||
ctx.line_to(x,y)
|
||||
|
||||
def render_string(self,ctx,s):
|
||||
xoff = 0
|
||||
yoff = 0
|
||||
ctx.set_line_width(1)
|
||||
for c in s:
|
||||
if c == '\r' or c == '\n':
|
||||
xoff = 0
|
||||
yoff += 10
|
||||
elif c == '\t':
|
||||
xoff += 20
|
||||
else:
|
||||
self.render_char(ctx,c,xoff,yoff)
|
||||
xoff += 10
|
||||
|
||||
class CairoPlotter:
|
||||
|
||||
def __init__(self, path):
|
||||
self.surface = cairo.SVGSurface(path, W, H)
|
||||
def __init__(self, path, surftype = 'SVG'):
|
||||
self.surftype = surftype
|
||||
if surftype == 'SVG':
|
||||
self.surface = cairo.SVGSurface(path, W, H)
|
||||
elif surftype == 'PNG':
|
||||
self.path = path
|
||||
self.surface = cairo.ImageSurface(cairo.Format.RGB24,W,H)
|
||||
self.context = cairo.Context(self.surface)
|
||||
if surftype == 'PNG':
|
||||
self.context.rectangle(0,0,W,H)
|
||||
self.context.set_source_rgb(1.0,1.0,1.0)
|
||||
self.context.fill()
|
||||
#self.context.scale(W,H)
|
||||
self.context.select_font_face("monospace")
|
||||
self.pennum = 0
|
||||
|
@ -32,7 +87,11 @@ class CairoPlotter:
|
|||
self.window = (0, 0, W, H)
|
||||
self.text_theta = 0
|
||||
self.text_size = 1
|
||||
self.context.set_line_cap(cairo.LINE_CAP_ROUND)
|
||||
self.context.set_line_join(cairo.LINE_JOIN_ROUND)
|
||||
self.update_ctm()
|
||||
self.char_font = Font('data/a410_chars.pickle')
|
||||
|
||||
|
||||
# A quick review of coordinate systems:
|
||||
# Cairo native: origin at upper left, x+ to right, y+ down.
|
||||
|
@ -67,11 +126,13 @@ class CairoPlotter:
|
|||
self.context.move_to(cp[0],cp[1])
|
||||
|
||||
def vp(self, params):
|
||||
self.finish_path()
|
||||
l=coordlist(params,4)
|
||||
self.viewport=(l[0],l[1],l[2],l[3])
|
||||
self.update_ctm()
|
||||
|
||||
def wd(self, params):
|
||||
self.finish_path()
|
||||
l=coordlist(params,4)
|
||||
self.window=(l[0],l[1],l[2],l[3])
|
||||
self.update_ctm()
|
||||
|
@ -108,6 +169,52 @@ class CairoPlotter:
|
|||
p=self.context.get_current_point()
|
||||
self.context.arc(p[0],p[1],r,0.0,math.pi*2)
|
||||
|
||||
def yt(self, params):
|
||||
l=coordlist(params,5)
|
||||
left = l[3]
|
||||
right = l[4]
|
||||
num = int(l[2])
|
||||
style = int(l[0])
|
||||
if style > 3:
|
||||
sys.stderr.write("Unrecognized tick mark style {}\n".format(style))
|
||||
return
|
||||
if style:
|
||||
dist = l[1]
|
||||
tw = dist/num
|
||||
else:
|
||||
dist = l[1]*num
|
||||
tw = l[1]
|
||||
first = style >> 1
|
||||
(x,y) = self.context.get_current_point()
|
||||
self.context.line_to(x,y+dist)
|
||||
for i in range(first,num+1):
|
||||
y1 = y + tw*i
|
||||
self.context.move_to(x+left,y1)
|
||||
self.context.line_to(x-right,y1)
|
||||
|
||||
def xt(self, params):
|
||||
l=coordlist(params,5)
|
||||
above = l[3]
|
||||
below = l[4]
|
||||
num = int(l[2])
|
||||
style = int(l[0])
|
||||
if style > 3:
|
||||
sys.stderr.write("Unrecognized tick mark style {}\n".format(style))
|
||||
return
|
||||
if style & 0x01:
|
||||
dist = l[1]
|
||||
tw = dist/num
|
||||
else:
|
||||
dist = l[1]*num
|
||||
tw = l[1]
|
||||
first = style >> 1
|
||||
(x,y) = self.context.get_current_point()
|
||||
self.context.line_to(x+dist,y)
|
||||
for i in range(first,num+1):
|
||||
x1 = x + tw*i
|
||||
self.context.move_to(x1,y+above)
|
||||
self.context.line_to(x1,y-below)
|
||||
|
||||
def ac(self,params):
|
||||
l=coordlist(params)
|
||||
r=l[0]
|
||||
|
@ -136,11 +243,20 @@ class CairoPlotter:
|
|||
def pl(self,params):
|
||||
self.finish_path()
|
||||
(x,y) = self.context.get_current_point()
|
||||
sys.stderr.write("text at {} {}\n".format(x,y))
|
||||
self.context.show_text(params)
|
||||
self.context.save()
|
||||
self.context.translate(x,y)
|
||||
self.context.rotate(math.radians(self.text_theta))
|
||||
self.context.scale(self.text_size/10.0, self.text_size/8.0)
|
||||
self.char_font.render_string(self.context,params)
|
||||
self.context.restore()
|
||||
|
||||
def write(self,out):
|
||||
def write(self):
|
||||
self.finish_path()
|
||||
if self.surftype == 'PNG':
|
||||
if self.path == '-':
|
||||
self.surface.write_to_png(sys.stdout)
|
||||
else:
|
||||
self.surface.write_to_png(self.path)
|
||||
self.surface.finish()
|
||||
|
||||
def process_cmd(self,command):
|
||||
|
@ -157,7 +273,7 @@ class CairoPlotter:
|
|||
self.process_cmd(line)
|
||||
|
||||
if __name__=='__main__':
|
||||
p = CairoPlotter('foo.svg')
|
||||
p = CairoPlotter(sys.stdout.buffer)
|
||||
p.read(sys.stdin)
|
||||
p.write(sys.stdout)
|
||||
p.write()
|
||||
|
||||
|
|
|
@ -1,175 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
import sys
|
||||
import svgwrite
|
||||
import math
|
||||
|
||||
def coordlist(line,expected=-1):
|
||||
l = list(map(float,line.split(',')))
|
||||
if expected > -1:
|
||||
assert len(l) == expected
|
||||
return l
|
||||
|
||||
pens = [
|
||||
svgwrite.rgb(0, 0, 0, '%'),
|
||||
svgwrite.rgb(100, 0, 0, '%'),
|
||||
svgwrite.rgb(0, 100, 0, '%'),
|
||||
svgwrite.rgb(0, 0, 100, '%') ]
|
||||
|
||||
# Default W/H
|
||||
W = 2394
|
||||
H = 1759
|
||||
|
||||
class Plotter:
|
||||
def __init__(self):
|
||||
self.pennum = 0
|
||||
self.viewport = (0, 0, W, H)
|
||||
self.window = (0, 0, W, H)
|
||||
self.cur_g = None
|
||||
self.cur_d = None
|
||||
self.d = svgwrite.Drawing(size=(W,H))
|
||||
self.text_theta = 0
|
||||
self.text_size = 1
|
||||
self.pos = (0,0)
|
||||
self.clip = None
|
||||
self.clipno = 0
|
||||
|
||||
def get_g(self):
|
||||
"Return a valid group for this viewport/window"
|
||||
if not self.cur_g:
|
||||
self.cur_g = self.d.g(style='clip-path: url(#cl{})'.format(self.clipno))
|
||||
(v,w) = (self.viewport,self.window)
|
||||
a = (v[2]-v[0])/(w[2]-w[0])
|
||||
d = (v[3]-v[1])/(w[3]-w[1])
|
||||
e = v[0] - w[0]*a
|
||||
f = v[1] - w[1]*d
|
||||
# everything needs to flip on the Y axis.
|
||||
f = H-f
|
||||
d = -d
|
||||
self.cur_g.matrix(a,0,0,d,e,f)
|
||||
self.clip = self.d.clipPath(id='cl{}'.format(self.clipno))
|
||||
self.clip.add(self.d.rect((w[0],w[1]),(w[2],w[3])))
|
||||
self.d.defs.add(self.clip)
|
||||
self.clipno += 1
|
||||
return self.cur_g
|
||||
|
||||
def finish_g(self):
|
||||
if self.cur_g:
|
||||
self.d.add(self.cur_g)
|
||||
self.cur_g = None
|
||||
|
||||
def finish_path(self):
|
||||
if self.cur_d:
|
||||
path=self.d.path(
|
||||
d=self.cur_d,
|
||||
stroke=pens[self.pennum])
|
||||
path.fill(opacity=0)
|
||||
self.get_g().add(path)
|
||||
self.cur_d = None
|
||||
|
||||
def add_to_path(self, addition):
|
||||
if not self.cur_d:
|
||||
self.cur_d = "M{} {}".format(self.pos[0],self.pos[1])
|
||||
self.cur_d = self.cur_d + " " + addition
|
||||
|
||||
def invalidate_window(self):
|
||||
self.finish_path()
|
||||
self.finish_g()
|
||||
|
||||
def vp(self, params):
|
||||
self.invalidate_window()
|
||||
l=coordlist(params,4)
|
||||
self.viewport=(l[0],l[1],l[2],l[3])
|
||||
|
||||
def wd(self, params):
|
||||
self.invalidate_window()
|
||||
l=coordlist(params,4)
|
||||
self.window=(l[0],l[1],l[2],l[3])
|
||||
|
||||
def ma(self, params):
|
||||
l=coordlist(params,2)
|
||||
self.pos = (l[0],l[1])
|
||||
|
||||
def mr(self, params):
|
||||
l=coordlist(params,2)
|
||||
self.pos = (self.pos[0]+l[0],self.pos[1]+l[1])
|
||||
|
||||
def da(self, params):
|
||||
l=coordlist(params)
|
||||
self.add_to_path("L{}".format(" ".join(map(str,l))))
|
||||
self.pos = (l[-2],l[-1])
|
||||
|
||||
def dr(self, params):
|
||||
l=coordlist(params)
|
||||
self.add_to_path("l{}".format(" ".join(map(str,l))))
|
||||
self.pos = (l[-2],l[-1])
|
||||
|
||||
def ca(self, params):
|
||||
self.finish_path()
|
||||
l=coordlist(params)
|
||||
r=l[0]
|
||||
if len(l) > 2:
|
||||
p=(l[1],l[2])
|
||||
else:
|
||||
p=self.pos
|
||||
c=self.d.circle(center=p,r=r)
|
||||
c.fill(opacity=0)
|
||||
c.stroke(color=pens[self.pennum],opacity=100,width=2)
|
||||
self.get_g().add(c)
|
||||
|
||||
def ac(self,params):
|
||||
l=coordlist(params)
|
||||
r=l[0]
|
||||
if len(l) > 3:
|
||||
p=(l[3],l[4])
|
||||
else:
|
||||
p=self.pos
|
||||
t2, t1 = math.radians(l[1]),math.radians(l[2])
|
||||
x1, y1 = p[0] + (r*math.cos(t1)), p[1] + (r*math.sin(t1))
|
||||
x2, y2 = p[0] + (r*math.cos(t2)), p[1] + (r*math.sin(t2))
|
||||
ds="M {} {} A {} {} 0 0 0 {} {}".format(x1,y1,r,r,x2,y2)
|
||||
self.add_to_path(ds)
|
||||
|
||||
|
||||
def lr(self,params):
|
||||
self.text_theta = float(params)
|
||||
|
||||
def ls(self,params):
|
||||
self.text_size = float(params)
|
||||
|
||||
def ps(self,params):
|
||||
self.finish_path()
|
||||
self.pennum = int(params) - 1
|
||||
|
||||
def pl(self,params):
|
||||
self.finish_path()
|
||||
flip_pos = (self.pos[0], -self.pos[1])
|
||||
t = self.d.text(params,insert=flip_pos)
|
||||
t.rotate(self.text_theta,center=flip_pos)
|
||||
t['font-size'] = self.text_size
|
||||
g = self.d.g()
|
||||
g.add(t)
|
||||
g.matrix(1,0,0,-1,0,0)
|
||||
self.get_g().add(g)
|
||||
|
||||
def write(self,out):
|
||||
self.invalidate_window()
|
||||
self.d.write(out)
|
||||
|
||||
def process_cmd(self,command):
|
||||
cmd_code = command[0:2].lower()
|
||||
params = command[2:]
|
||||
try:
|
||||
getattr(self,cmd_code)(params)
|
||||
except AttributeError:
|
||||
sys.stderr.write("Unrecognized command code {}\n".format(cmd_code))
|
||||
|
||||
def read(self,inf):
|
||||
for line in inf.readlines():
|
||||
line = line.strip()
|
||||
self.process_cmd(line)
|
||||
|
||||
if __name__=='__main__':
|
||||
p = Plotter()
|
||||
p.read(sys.stdin)
|
||||
p.write(sys.stdout)
|
||||
|
|
@ -132,6 +132,14 @@ specified the circle will be centered at the current position.
|
|||
|
||||
Params: 1 or 3
|
||||
|
||||
### Letter Initialize (LI)
|
||||
|
||||
```
|
||||
LI
|
||||
```
|
||||
|
||||
Reset the text format.
|
||||
|
||||
### Letter size (LS)
|
||||
|
||||
```
|
||||
|
@ -176,14 +184,15 @@ Params: 1
|
|||
### Line type (LT)
|
||||
|
||||
```
|
||||
LTn
|
||||
LTp(,k)
|
||||
```
|
||||
|
||||
Select the line style for subsequent draw operations. `n` is a value in the
|
||||
Select the line style for subsequent draw operations. `p` is a value in the
|
||||
range 1-9:
|
||||
|
||||
| n | Line style |
|
||||
|---|------------------|
|
||||
| 0 | Custom pattern (see UL) |
|
||||
| 1 | Solid |
|
||||
| 2 | Dotted |
|
||||
| 3 | Short dash |
|
||||
|
@ -194,7 +203,18 @@ range 1-9:
|
|||
| 8 | Line w/ two dots |
|
||||
| 9 | Line w/ two short dashes |
|
||||
|
||||
Params: 1
|
||||
`k` specifies the repeat length in mesurement units. The default is 100, but the value will persist.
|
||||
|
||||
Params: 1 or 2
|
||||
|
||||
```
|
||||
ULd1,m1(,d2,m3,..d6,m6)
|
||||
```
|
||||
|
||||
Create a custom pattern for use with subsequent LT0 operations.
|
||||
|
||||
`d` specifies a draw length, `m` specifies a move length.
|
||||
|
||||
|
||||
### X-axis ticks (XT)
|
||||
|
||||
|
@ -214,9 +234,15 @@ is interpreted.
|
|||
| a | length of tick mark above the axis |
|
||||
| b | length of tick mark below the axis |
|
||||
|
||||
If the `s` parameter is set to 1, then the distance is interpreted as
|
||||
the length of the entire axis. If `s` is set to 2, then the distance is
|
||||
interpretes as the length of the space between adjacent tick marks.
|
||||
The `s` parameter determines how the `d` parameter is interpreted and whether
|
||||
the first tick mark (at the "0" point on the axis) is drawn or not:
|
||||
|
||||
| style parameter | description |
|
||||
|------------------|-------------------------------------|
|
||||
| 0 | d is distance between ticks |
|
||||
| 1 | d is total length of axis |
|
||||
| 2 | d is distance between ticks, first tick mark is not drawn |
|
||||
| 3 | d is total length of axis, first tick mark is not drawn |
|
||||
|
||||
Params: 5
|
||||
|
||||
|
@ -299,7 +325,7 @@ WDx1,y1,x2,y2
|
|||
|
||||
The window into which to draw. See the introduction for an explanation of how the window
|
||||
is used. `x1,y1` specify the upper left hand corner
|
||||
of the viewport; `x2,y2` specify the lower right hand of the viewport.
|
||||
of the window; `x2,y2` specify the lower right hand of the window.
|
||||
|
||||
Params: 4
|
||||
|
||||
|
@ -329,20 +355,58 @@ After the arc is drawn, the endpoint of the arc is considered to be the new posi
|
|||
|
||||
Params: 3 or 5
|
||||
|
||||
### Unknown (IM)
|
||||
### Error action (IM)
|
||||
|
||||
```
|
||||
IMa,b
|
||||
IMa(,b)
|
||||
```
|
||||
|
||||
IM0,31 turns on error checking; IM0,0 turns it off?
|
||||
This is referenced obliquely in part II of the manual.
|
||||
Selecting error action
|
||||
|
||||
`a` specifies the mode: 0 = turn on error light; 64 = reject commands ; 192 = write message.
|
||||
|
||||
| a | Mode |
|
||||
|---|------------------|
|
||||
| 0 | Turn on error light |
|
||||
| 64 | Reject command |
|
||||
| 192 | Write error message |
|
||||
|
||||
Do note that "write message" does not mean to send a response over the serial port; it means
|
||||
that it will literally write the error message out on the plotter.
|
||||
|
||||
`b` specifies the errors to filter. Default is 31.
|
||||
|
||||
| a | Error type |
|
||||
|---|------------------|
|
||||
| 1 | Command not recognized |
|
||||
| 2 | Numbers outside range |
|
||||
| 4 | Not enough numbers for command |
|
||||
| 8 | Bad syntax |
|
||||
| 16 | Pen would go outside window |
|
||||
|
||||
### Size Paper (SP)
|
||||
|
||||
```SPn```
|
||||
|
||||
Sets the current paper size. This is essentially a preset "VP" command. The table below of
|
||||
drawing area sizes is approximate, based on measuring a rectangle drawn at the extent of the
|
||||
entire viewport.
|
||||
|
||||
| n | measurement (in mm) |
|
||||
|---|---------------------|
|
||||
| 0 | 241 x 175 |
|
||||
| 1 | 178 x 240 |
|
||||
| 2 | 394 x 240 |
|
||||
| 3 | 282 x 280(?) |
|
||||
| 4 | 259 x 170 |
|
||||
| 5 | 171 x 280(?) |
|
||||
| 6 | 326 x 217 |
|
||||
| 7 | 218 x 142 |
|
||||
| 8 | 143 x 217 |
|
||||
|
||||
## Unknown commands
|
||||
|
||||
LI - ??? - no params/arb ???
|
||||
PK - ??? - no params/arb
|
||||
UL - ??? - no params/arb ? 1 param 0-9?
|
||||
SP - ??? - 1 param ??
|
||||
LF - ??? - 1 param ? 0-9
|
||||
PK - ??? - no params/arb (Not in user reference)
|
||||
|
||||
LF - ??? - 1 param ? 0-9 Letter Font
|
||||
|
||||
|
|
412
docs/Apple 410 Plotter.md
Normal file
412
docs/Apple 410 Plotter.md
Normal file
|
@ -0,0 +1,412 @@
|
|||
# The Apple 410 Color Plotter
|
||||
|
||||
## Quick introduction and apology
|
||||
|
||||
Everything below (other than the dip switch settings) was sussed out by
|
||||
ripping the ROM, extracting the self-test script and the command lookup table, and
|
||||
trial and error. Now that the manual is included in this folder, this could possibly
|
||||
be updated.
|
||||
|
||||
## General concepts
|
||||
|
||||
The way the 401 interprets a coordinate at any given time depends on the current
|
||||
viewport and window settings. The "viewport" is a rectangle specified in hardware
|
||||
absolute coordinates and defines the physical area in which coordinates are resolved.
|
||||
The "window" defines the virtual coordinates of the drawing area, which are then
|
||||
mapped to the viewport.
|
||||
|
||||
All drawing operations are clipped to the current viewport and window; parts of
|
||||
arcs, circles, lines, etc. that would be rendered outside the viewport are not
|
||||
drawn.
|
||||
|
||||
If the size of the window has a different aspect ratio than the size of the viewport,
|
||||
the elements drawn will be stretched to fit. You can use this mechanism to draw ellipses
|
||||
or stretched text.
|
||||
|
||||
Commands sent to the 401 are terminated by the ASCII "end of text" delimiter, 0x03.
|
||||
A semicolon or newline may also be used in some situations; however it's safest to
|
||||
terminate a text-writing command (PL) with 0x03.
|
||||
|
||||
## Units
|
||||
|
||||
The base units for the viewport (as used in the VP command) are approximately 1 unit
|
||||
= 0.1mm. (In practice, this is slightly off, so I don't recommend drawing any rulers
|
||||
with this machine.)
|
||||
|
||||
### Serial connection
|
||||
|
||||
The 8 DIP switches (SW1-SW8) on the back of the plotter are used to configure the
|
||||
serial interface.
|
||||
|
||||
SW1 selects the data bits. ON = 7 bits, OFF = 8 bits.
|
||||
|
||||
SW2 selects whether parity is used. ON = no parity bit, OFF = use a parity bit.
|
||||
If parity is selected, SW3 determines the type of parity bit. ON = odd parity,
|
||||
OFF = even parity.
|
||||
|
||||
SW4 and SW5 select the number of stop bits:
|
||||
|
||||
| SW4 | SW5 | Stop bits |
|
||||
|------|------|---------------|
|
||||
| OFF | OFF | 2 stop bits |
|
||||
| ON | OFF | 1.5 stop bits |
|
||||
| OFF | ON | 1 stop bit |
|
||||
| ON | ON | invalid |
|
||||
|
||||
SW6, SW7, and SW8 select the baud rate:
|
||||
|
||||
| SW6 | SW7 | SW8 | Baud |
|
||||
|-----|-----|-----|------------|
|
||||
| OFF | OFF | OFF | 75 baud |
|
||||
| OFF | OFF | ON | 150 baud |
|
||||
| OFF | ON | OFF | 300 baud |
|
||||
| OFF | ON | ON | 600 baud |
|
||||
| ON | OFF | OFF | 1200 baud |
|
||||
| ON | OFF | ON | 2400 baud |
|
||||
| ON | ON | OFF | 4800 baud |
|
||||
| ON | ON | ON | 9600 baud |
|
||||
|
||||
**The 401 uses hardware handshaking.** It never returns data to the computer
|
||||
over the serial line and sets DSR when its internal buffer is full. Sending even
|
||||
one byte after the DSR line has been set will corrupt the 401's command buffer
|
||||
and trigger an error. Most USB to RS232 cables will attempt to bundle up several
|
||||
bytes before transmission, so unless you're sure that your cable can handle it
|
||||
I recommend flushing the serial connection after every byte and checking the DSR
|
||||
line yourself.
|
||||
|
||||
## Command Reference
|
||||
|
||||
### Move Absolute (MA)
|
||||
|
||||
```
|
||||
MAx,y
|
||||
```
|
||||
|
||||
Raise the pen and move the plotter head to the position specified by the x,y coordinates.
|
||||
|
||||
Params: 2
|
||||
|
||||
### Move relative (MR)
|
||||
|
||||
```
|
||||
MRx,y
|
||||
```
|
||||
|
||||
Raise the pen and move the plotter head to a position offset x,y from its
|
||||
current position.
|
||||
|
||||
Params: 2
|
||||
|
||||
### Draw absolute (DA)
|
||||
|
||||
```
|
||||
DAx,y(,x,y..)
|
||||
```
|
||||
|
||||
Lower the pen and draw a line from the current position to the position
|
||||
specified by the x,y coordinates. If more than one coordinate pair is
|
||||
specified, continues to draw lines to the subsequent points as well.
|
||||
|
||||
Params: 2(+2*n)
|
||||
|
||||
### Draw relative (DR)
|
||||
|
||||
```
|
||||
DRx,y(,x,y..)
|
||||
```
|
||||
|
||||
Lower the pen and draw a line from the current position to the position
|
||||
offset x,y for its current position. If more than one coordinate pair is
|
||||
specified, continues to draw lines to the subsequent offset points as well.
|
||||
|
||||
Params: 2(+2*n)
|
||||
|
||||
### Circle (CA)
|
||||
|
||||
```
|
||||
CAr(,x,y)
|
||||
```
|
||||
|
||||
Lower the pen and draw a circle of radius r centered at x,y. If x,y are not
|
||||
specified the circle will be centered at the current position.
|
||||
|
||||
Params: 1 or 3
|
||||
|
||||
### Letter Initialize (LI)
|
||||
|
||||
```
|
||||
LI
|
||||
```
|
||||
|
||||
Reset the text format.
|
||||
|
||||
### Letter size (LS)
|
||||
|
||||
```
|
||||
LSs
|
||||
```
|
||||
|
||||
Sets the font size to s (?? in what units? relative to what system of coordinates? ??)
|
||||
|
||||
Params: 1
|
||||
|
||||
### Letter rotation (LR)
|
||||
|
||||
```
|
||||
LRθ
|
||||
```
|
||||
|
||||
Sets the angle to draw subsequent text at θ degrees clockwise.
|
||||
|
||||
Params: 1
|
||||
|
||||
### Print letters (PL)
|
||||
|
||||
```
|
||||
PLtext
|
||||
```
|
||||
|
||||
Lower the pen and draw the specified text at the current position,
|
||||
using the currently active letter size and rotation settings.
|
||||
|
||||
Params : 1 (string)
|
||||
|
||||
### Pen select (PS)
|
||||
|
||||
```
|
||||
PSi
|
||||
```
|
||||
|
||||
Select pen i (where i is in the range 1-4).
|
||||
|
||||
Params: 1
|
||||
|
||||
### Line type (LT)
|
||||
|
||||
```
|
||||
LTp(,k)
|
||||
```
|
||||
|
||||
Select the line style for subsequent draw operations. `p` is a value in the
|
||||
range 1-9:
|
||||
|
||||
| n | Line style |
|
||||
|---|------------------|
|
||||
| 0 | Custom pattern (see UL) |
|
||||
| 1 | Solid |
|
||||
| 2 | Dotted |
|
||||
| 3 | Short dash |
|
||||
| 4 | Medium dash |
|
||||
| 5 | Long dash |
|
||||
| 6 | Line w/ dots |
|
||||
| 7 | Line w/ one short dash |
|
||||
| 8 | Line w/ two dots |
|
||||
| 9 | Line w/ two short dashes |
|
||||
|
||||
`k` specifies the repeat length in mesurement units. The default is 100, but the value will persist.
|
||||
|
||||
Params: 1 or 2
|
||||
|
||||
```
|
||||
ULd1,m1(,d2,m3,..d6,m6)
|
||||
```
|
||||
|
||||
Create a custom pattern for use with subsequent LT0 operations.
|
||||
|
||||
`d` specifies a draw length, `m` specifies a move length.
|
||||
|
||||
|
||||
### X-axis ticks (XT)
|
||||
|
||||
```
|
||||
XTs,d,n,a,b
|
||||
```
|
||||
|
||||
Draw an X axis with tick marks as specified. There are two "styles"
|
||||
`s` of input available, which determine how the distance `d` parameter
|
||||
is interpreted.
|
||||
|
||||
| parameter | function |
|
||||
|-----------|---------------------------------------|
|
||||
| s | style |
|
||||
| d | distance |
|
||||
| n | number of tick marks |
|
||||
| a | length of tick mark above the axis |
|
||||
| b | length of tick mark below the axis |
|
||||
|
||||
The `s` parameter determines how the `d` parameter is interpreted and whether
|
||||
the first tick mark (at the "0" point on the axis) is drawn or not:
|
||||
|
||||
| style parameter | description |
|
||||
|------------------|-------------------------------------|
|
||||
| 0 | d is distance between ticks |
|
||||
| 1 | d is total length of axis |
|
||||
| 2 | d is distance between ticks, first tick mark is not drawn |
|
||||
| 3 | d is total length of axis, first tick mark is not drawn |
|
||||
|
||||
Params: 5
|
||||
|
||||
### Y-axis ticks (YT)
|
||||
|
||||
```
|
||||
YTs,d,n,a,b
|
||||
```
|
||||
|
||||
Draw a Y axis with tick marks as specified. The parameters are handled
|
||||
in the same manner as the `XT` command (see above).
|
||||
|
||||
Params: 5
|
||||
|
||||
### Clear/Reset (CH)
|
||||
|
||||
```
|
||||
CH
|
||||
```
|
||||
|
||||
Raise the pen and move the plotter head to its original position; usually used
|
||||
at the end of a plot.
|
||||
|
||||
Params: 0
|
||||
|
||||
### Point mark (PM)
|
||||
|
||||
```
|
||||
PMt
|
||||
```
|
||||
|
||||
Draw a marker at the current position. The marker type is specified by the `t`
|
||||
parameter. The marker size is determined by the font size setting specified by the
|
||||
`LS` command; consider the markers as just special characters.
|
||||
|
||||
The various marker shapes are illustrated in the images below.
|
||||
|
||||
![Image of PM1-5](pm1-5.jpg) ![Image of PM6-10](pm6-10.jpg) ![Image of PM11-15](pm11-15.jpg)
|
||||
|
||||
Params: 1
|
||||
|
||||
### Pen velocity (PV)
|
||||
|
||||
```
|
||||
PVv
|
||||
```
|
||||
|
||||
Sets the drawing velocity `v`, on a scale of 1-10, where 1 is slowest, 10 is fastest.
|
||||
The default speed is 10.
|
||||
|
||||
Params: 1
|
||||
|
||||
### Slant lettering (SL)
|
||||
|
||||
```
|
||||
SLθ
|
||||
```
|
||||
|
||||
Slants subsequent text by an angle specified by `θ` degrees.
|
||||
|
||||
Params: 1
|
||||
|
||||
### Viewport (VP)
|
||||
|
||||
```
|
||||
VPx1,y1,x2,y2
|
||||
```
|
||||
|
||||
Specifies the viewport area, in absolute coordinates, to draw in. See the introduction
|
||||
for an explanation of how the viewport is used. `x1,y1` specify the upper left hand corner
|
||||
of the viewport; `x2,y2` specify the lower right hand of the viewport.
|
||||
|
||||
Params: 4
|
||||
|
||||
### Window (WD)
|
||||
|
||||
```
|
||||
WDx1,y1,x2,y2
|
||||
```
|
||||
|
||||
The window into which to draw. See the introduction for an explanation of how the window
|
||||
is used. `x1,y1` specify the upper left hand corner
|
||||
of the window; `x2,y2` specify the lower right hand of the window.
|
||||
|
||||
Params: 4
|
||||
|
||||
### Reset (RS)
|
||||
|
||||
```
|
||||
RS
|
||||
```
|
||||
|
||||
Resets the error bit. If the error LED is lit, it should be extinguished.
|
||||
|
||||
RS will apparently accept some parameters, but it's unknown if this does anything.
|
||||
|
||||
Params: 0 or 1
|
||||
|
||||
### Arc (AC)
|
||||
|
||||
```
|
||||
ACr,θ1,θ2(,x,y)
|
||||
```
|
||||
|
||||
Draws a circular arc of radius `r` from `θ1` to `θ2`. If x,y are specified, they
|
||||
determined the center of the arc's circle. If they are not specified, the current
|
||||
position is considered to be the center of the arc's circle.
|
||||
|
||||
After the arc is drawn, the endpoint of the arc is considered to be the new position.
|
||||
|
||||
Params: 3 or 5
|
||||
|
||||
### Error action (IM)
|
||||
|
||||
```
|
||||
IMa(,b)
|
||||
```
|
||||
|
||||
Selecting error action
|
||||
|
||||
`a` specifies the mode: 0 = turn on error light; 64 = reject commands ; 192 = write message.
|
||||
|
||||
| a | Mode |
|
||||
|---|------------------|
|
||||
| 0 | Turn on error light |
|
||||
| 64 | Reject command |
|
||||
| 192 | Write error message |
|
||||
|
||||
Do note that "write message" does not mean to send a response over the serial port; it means
|
||||
that it will literally write the error message out on the plotter.
|
||||
|
||||
`b` specifies the errors to filter. Default is 31.
|
||||
|
||||
| a | Error type |
|
||||
|---|------------------|
|
||||
| 1 | Command not recognized |
|
||||
| 2 | Numbers outside range |
|
||||
| 4 | Not enough numbers for command |
|
||||
| 8 | Bad syntax |
|
||||
| 16 | Pen would go outside window |
|
||||
|
||||
### Size Paper (SP)
|
||||
|
||||
```SPn```
|
||||
|
||||
Sets the current paper size. This is essentially a preset "VP" command. The table below of
|
||||
drawing area sizes is approximate, based on measuring a rectangle drawn at the extent of the
|
||||
entire viewport.
|
||||
|
||||
| n | measurement (in mm) |
|
||||
|---|---------------------|
|
||||
| 0 | 241 x 175 |
|
||||
| 1 | 178 x 240 |
|
||||
| 2 | 394 x 240 |
|
||||
| 3 | 282 x 280(?) |
|
||||
| 4 | 259 x 170 |
|
||||
| 5 | 171 x 280(?) |
|
||||
| 6 | 326 x 217 |
|
||||
| 7 | 218 x 142 |
|
||||
| 8 | 143 x 217 |
|
||||
|
||||
## Unknown commands
|
||||
|
||||
PK - ??? - no params/arb (Not in user reference)
|
||||
|
||||
LF - ??? - 1 param ? 0-9 Letter Font
|
||||
|
BIN
docs/Apple410Plotter.PDF
Executable file
BIN
docs/Apple410Plotter.PDF
Executable file
Binary file not shown.
58
models/plotter_head.scad
Normal file
58
models/plotter_head.scad
Normal file
|
@ -0,0 +1,58 @@
|
|||
5notch_width = 2.5;
|
||||
$fa = 6;
|
||||
$fs = 0.5;
|
||||
module peg_hole() {
|
||||
union() {
|
||||
difference() {
|
||||
union() {
|
||||
cylinder(h=21.5,d1=7.8,d2=7.5,center=false);
|
||||
// Chamfer
|
||||
cylinder(h=1.25,d1=9.2,d2=7.7,center=false);
|
||||
}
|
||||
translate([3.3, -5, -0.1]) {
|
||||
cube([10,10,30]);
|
||||
}
|
||||
}
|
||||
translate([0,-notch_width/2,0]) {
|
||||
cube([4.5,notch_width,3.4]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module shell() {
|
||||
union() {
|
||||
cylinder(h=12,d=32.0,center=false);
|
||||
translate([0,0,8.1]) {
|
||||
cylinder(h=3.4,d=34,center=false);
|
||||
}
|
||||
translate([0,0,11.5]) {
|
||||
cylinder(h=11.4,d1=34,d2=21,center=false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module pen(which) {
|
||||
rotate([0,0,which*90]) {
|
||||
translate([10,0,4]) rotate([0,90,0]) {
|
||||
cylinder(h=10,d=4,center=false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module pens() {
|
||||
for (i=[0:4]) {
|
||||
pen(i);
|
||||
}
|
||||
}
|
||||
|
||||
//peg_hole();
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
shell();
|
||||
}
|
||||
pens();
|
||||
translate([0,0,-0.005]) {
|
||||
peg_hole();
|
||||
}
|
||||
}
|
3684
roms/Apple410.sfd
Normal file
3684
roms/Apple410.sfd
Normal file
File diff suppressed because it is too large
Load Diff
BIN
roms/Apple410.ttf
Normal file
BIN
roms/Apple410.ttf
Normal file
Binary file not shown.
|
@ -8,3 +8,11 @@ consolidated ROM image built by concatenating the three individual roms.
|
|||
`test_extractor.py` is a script for extracting the self-test commands from the consolidated
|
||||
ROM file.
|
||||
|
||||
'font_extract.py' extracts the font as a set of SVG files. 'font_to_pickled_dict.py' generates
|
||||
two pickle files, one with the font and another with the point markers. Each pickle file contains
|
||||
a single dictionary mapping characters to the raw sequence of bytes that represents the glyph in
|
||||
the Apple 410's native format.
|
||||
|
||||
'ff_import.py' is a rough script for importing the SVGs into fontforge. The generated font will
|
||||
require a lot of manual cleanup.
|
||||
|
||||
|
|
37
roms/ff_import.py
Executable file
37
roms/ff_import.py
Executable file
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import fontforge
|
||||
import tempfile
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
font = fontforge.font()
|
||||
font.fontname = "Apple410"
|
||||
base = "alpha"
|
||||
matcher = re.compile(r"{}_([0-9a-f][0-9a-f]).svg".format(base))
|
||||
|
||||
for path in os.listdir('.'):
|
||||
m = matcher.match(path)
|
||||
if m:
|
||||
idx = int(m.group(1),16)
|
||||
print "{} : {}".format(idx,path)
|
||||
# fontforge can't handle % colors, so we need to hack those out. Luckily, these are the only %
|
||||
# characters in the files, so...
|
||||
tf = tempfile.NamedTemporaryFile(suffix='.svg')
|
||||
inf = open(path)
|
||||
for c in inf.read():
|
||||
if c != '%':
|
||||
tf.write(c)
|
||||
tf.flush()
|
||||
font.createChar(idx)
|
||||
font[idx].importOutlines(tf.name)
|
||||
tf.close()
|
||||
inf.close()
|
||||
|
||||
print "glyph count {}".format(len(font))
|
||||
#print font.createChar(0x41)
|
||||
#font[0x41].importOutlines("alpha_41.svg")
|
||||
|
||||
font.save("Apple410-new.sfd")
|
||||
|
71
roms/font_extract.py
Executable file
71
roms/font_extract.py
Executable file
|
@ -0,0 +1,71 @@
|
|||
#!/usr/bin/python3
|
||||
import sys
|
||||
import math
|
||||
import cairo
|
||||
import struct
|
||||
|
||||
alpha_ft_start = 0x2569
|
||||
point_ft_start = 0x3153
|
||||
|
||||
f = open("ROM.bin","rb")
|
||||
data = f.read()
|
||||
|
||||
|
||||
def eot(ft, off):
|
||||
"Returns true if the offset is at the end of the table."
|
||||
# Presumes that every table terminates in 0xff
|
||||
return data[ft + off*2] == 0xff
|
||||
|
||||
def get_char(ft,off):
|
||||
achar = chr(off+0x20)
|
||||
o1 = ft + (off*2)
|
||||
o2 = (data[o1+1]*256) + data[o1]
|
||||
o3 = o2
|
||||
while data[o3] != 0xff:
|
||||
o3 += 1
|
||||
return data[o2:o3]
|
||||
|
||||
def unpack_byte(b):
|
||||
"convert two 4-bit signed packed numbers to a tuple"
|
||||
# this packing is... unusual.
|
||||
x = b >> 4
|
||||
y = b % 16
|
||||
if y > 8: # now the weird
|
||||
x -= 1
|
||||
y -= 16
|
||||
return (x,y)
|
||||
|
||||
# H:W for a char is 3:2
|
||||
def unpack_coords(b,xscale=66,yscale=100,xoff=25,yoff=25):
|
||||
"convert two 4-bit signed packed numbers to cairo coordinates"
|
||||
(x,y) = unpack_byte(b)
|
||||
return (x*xscale + xoff, (8 - y)*yscale + yoff)
|
||||
|
||||
def build_char_file(path, ft, offset):
|
||||
surf = cairo.SVGSurface(path, 800, 1280)
|
||||
c = cairo.Context(surf)
|
||||
d = list(get_char(ft, offset))
|
||||
#c.set_source_rgb(0.0, 0.0, 0.0)
|
||||
c.set_line_width(40)
|
||||
c.set_line_cap(cairo.LINE_CAP_ROUND)
|
||||
c.set_line_join(cairo.LINE_JOIN_ROUND)
|
||||
while d:
|
||||
cmd = d.pop(0)
|
||||
cn, ca = cmd >> 4, cmd % 16
|
||||
for _ in range(ca):
|
||||
(x,y) = unpack_coords(d.pop(0))
|
||||
if cn == 0:
|
||||
c.move_to(x,y)
|
||||
elif cn == 2:
|
||||
c.line_to(x,y)
|
||||
c.stroke()
|
||||
surf.finish()
|
||||
|
||||
def dump_font(path_base, ft, charoff):
|
||||
off = 0
|
||||
while not eot(ft, off):
|
||||
build_char_file("{}_{:2x}.svg".format(path_base,charoff+off),ft,off)
|
||||
off += 1
|
||||
|
||||
dump_font("alpha",alpha_ft_start,0x20)
|
||||
|
92
roms/font_test.py
Executable file
92
roms/font_test.py
Executable file
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/python3
|
||||
import sys
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, Gdk
|
||||
import math
|
||||
|
||||
font_table_start = 0x2569
|
||||
first_char = 0x2627
|
||||
|
||||
f = open("ROM.bin","rb")
|
||||
data = f.read()
|
||||
|
||||
from gi.repository import Gtk
|
||||
import cairo
|
||||
import math
|
||||
|
||||
char_off = 0
|
||||
|
||||
# ! : 01 08 21 02 01 00 21 00
|
||||
# " : 01 28 21 26 01 68 21 66
|
||||
def get_char(ft,off):
|
||||
achar = chr(off+0x20)
|
||||
o1 = ft + (off*2)
|
||||
o2 = (data[o1+1]*256) + data[o1]
|
||||
o3 = o2
|
||||
while data[o3] != 0xff:
|
||||
o3 += 1
|
||||
#print("Looking up '{}' at {:x}; len {}".format(achar,o2,o3-o2))
|
||||
print("{} : {}".format(achar, " ".join(map(hex,data[o2:o3]))))
|
||||
return data[o2:o3]
|
||||
|
||||
def bytecoords(b):
|
||||
rx, ry = math.floor(b/16), b%16
|
||||
if ry > 8:
|
||||
ry = ry - 16
|
||||
ry = 8 - ry # inverted y axis
|
||||
return ( 50 + rx*16, 50 + ry*16 )
|
||||
|
||||
def OnDraw(w, cr):
|
||||
global char_off
|
||||
cr.set_source_rgb(0, 0, 0)
|
||||
np = True
|
||||
cr.set_line_width(5)
|
||||
cr.set_line_cap(cairo.LINE_CAP_ROUND)
|
||||
data = list(get_char(font_table_start, char_off))
|
||||
pos = (0,0)
|
||||
while data:
|
||||
c = data.pop(0)
|
||||
if c == 0x01:
|
||||
# move
|
||||
(x,y) = bytecoords( data.pop(0) )
|
||||
cr.move_to(x,y)
|
||||
elif c > 0x20:
|
||||
segments = c - 0x20
|
||||
for s in range(segments):
|
||||
# draw to
|
||||
(x,y) = bytecoords( data.pop(0) )
|
||||
cr.line_to(x,y)
|
||||
cr.stroke()
|
||||
|
||||
def OnKey(w, event):
|
||||
global char_off
|
||||
n = Gdk.keyval_name(event.keyval)
|
||||
if n == 'Right':
|
||||
char_off += 1
|
||||
#get_char(font_table_start, char_off)
|
||||
#coords.append((x,y))
|
||||
w.queue_draw()
|
||||
elif n == 'Left':
|
||||
char_off -= 1
|
||||
#get_char(font_table_start, char_off)
|
||||
w.queue_draw()
|
||||
elif n == 'q':
|
||||
print("QUIT")
|
||||
Gtk.main_quit()
|
||||
|
||||
w = Gtk.Window()
|
||||
w.set_default_size(640, 480)
|
||||
a = Gtk.DrawingArea()
|
||||
w.add(a)
|
||||
|
||||
w.connect('destroy', Gtk.main_quit)
|
||||
a.connect('draw', OnDraw)
|
||||
w.connect('key_press_event', OnKey)
|
||||
|
||||
w.show_all()
|
||||
|
||||
Gtk.main()
|
||||
|
||||
|
||||
|
48
roms/font_to_pickled_dict.py
Executable file
48
roms/font_to_pickled_dict.py
Executable file
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/python3
|
||||
"""
|
||||
Extract the fonts and create pickled dictionaries mapping characters to the raw binary
|
||||
strings representing each entry. Parsing these strings is done in the client code.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import pickle
|
||||
|
||||
alpha_ft_start = 0x2569
|
||||
point_ft_start = 0x3153
|
||||
|
||||
f = open("ROM.bin","rb")
|
||||
data = f.read()
|
||||
|
||||
def eot(ft, off):
|
||||
"Returns true if the offset is at the end of the table."
|
||||
# Presumes that every table terminates in 0xff
|
||||
return data[ft + off*2] == 0xff
|
||||
|
||||
def get_char(ft,off):
|
||||
"Returns the character data (sans terminating 0xff)"
|
||||
achar = chr(off+0x20)
|
||||
o1 = ft + (off*2)
|
||||
o2 = (data[o1+1]*256) + data[o1]
|
||||
o3 = o2
|
||||
while data[o3] != 0xff:
|
||||
o3 += 1
|
||||
return data[o2:o3]
|
||||
|
||||
|
||||
def build_char_dict(ft, first_char=0):
|
||||
offset = 0
|
||||
d = {}
|
||||
while not eot(ft,offset):
|
||||
cur_char = chr(offset+first_char)
|
||||
d[cur_char] = get_char(ft,offset)
|
||||
offset += 1
|
||||
return d
|
||||
|
||||
def pickle_font(path, ft, first_char=0):
|
||||
f = open(path,"wb")
|
||||
d = build_char_dict(ft,first_char)
|
||||
pickle.dump(d,f)
|
||||
|
||||
pickle_font("a410_chars.pickle",alpha_ft_start,ord(' '))
|
||||
pickle_font("a410_points.pickle",point_ft_start)
|
||||
|
4
setup.py
4
setup.py
|
@ -43,7 +43,8 @@ setup(
|
|||
|
||||
# You can just specify the packages manually here if your project is
|
||||
# simple. Or you can use find_packages().
|
||||
packages=find_packages(exclude=['contrib', 'docs', 'tests']),
|
||||
packages=['apple410'],
|
||||
package_data={'apple410' : ['data/*.pickle']},
|
||||
install_requires=['pyserial'],
|
||||
|
||||
python_requires='>=3',
|
||||
|
@ -55,6 +56,7 @@ setup(
|
|||
'apple410=apple410.cmdline:main',
|
||||
'a410svg2plot=apple410.cmdline:svg2plot',
|
||||
'a410plot2svg=apple410.cmdline:plot2svg',
|
||||
'a410plot2png=apple410.cmdline:plot2png',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue
Block a user