M
Matthew Moss
Here is my solution... Uses a cheap trick (a la method_missing) to
allow inline Postscript as method calls (so not as postfix). The
output is dumped to standard output to be redirected to a .ps file,
which can be easily viewed on Mac OS X (and almost as easily on other
platforms using ghostscript).
require 'matrix'
# Class helpers
class Array
def tail
self[1..-1]
end
def mod_fetch i
self[i % size]
end
def each_pair
each_with_index do |a, i|
yield a, self[i+1] if i+1 < size
end
end
end
class Vector
def x
self[0]
end
def y
self[1]
end
def len
Math.sqrt(inner_product(self))
end
def rot90
Vector[-y, x]
end
def rot45 # cheap rotate by 45 degrees
Vector[x - y, x + y]
end
def to_s
"#{x} #{y}"
end
end
# Postscript class (what a hack!)
class PS
def initialize(&block)
@cmds =3D []
instance_eval(&block) if block
end
def push(*args, &block)
@cmds << args.join(' ')
@cmds << instance_eval(&block) if block
end
def to_s
@cmds.join("\n")
end
def page(&block)
instance_eval(&block)
push 'showpage'
end
def path(&block)
push 'newpath'
instance_eval(&block)
end
def gsave(&block)
push 'gsave'
instance_eval(&block)
push 'grestore'
end
def method_missing(name, *args)
push *args + [name]
end
end
# Constants and helper funcs for building image data
Basis =3D [Vector[1, 0], Vector[0, -1], Vector[-1, 0], Vector[0, 1]]
Shade =3D [0.3, 0.5, 0.7]
def fibo(n)
a, b =3D 1, 1
n.times { a, b =3D b, a + b }
a
end
def spiral(n)
if n.zero?
Vector[0, 0]
else
i =3D n - 1
spiral(i) + Basis.mod_fetch(i) * fibo(i)
end
end
# Build list of spiral coordinates
steps =3D (ARGV[0] || 11).to_i
coords =3D (0..steps).map { |i| spiral(i).rot45 }
# Calculate page/content dimensions, scale and center
inch =3D 72
margin =3D 0.5 * inch
pagew =3D 8.5 * inch
pageh =3D 11 * inch
contw =3D pagew - 2 * margin
conth =3D pageh - 2 * margin
xmin =3D coords.min { |a, b| a.x <=3D> b.x }.x
xmax =3D coords.max { |a, b| a.x <=3D> b.x }.x
ymin =3D coords.min { |a, b| a.y <=3D> b.y }.y
ymax =3D coords.max { |a, b| a.y <=3D> b.y }.y
scale =3D [contw / (xmax - xmin), conth / (ymax - ymin)].min
cx =3D (pagew - (xmax - xmin.abs) * scale) / 2
cy =3D (pageh - (ymax - ymin.abs) * scale) / 2
# Scale coords to fill page
coords.map! { |v| v * scale }
# Build Postscript image
doc =3D PS.new do
def box a, b
l, r =3D [a.x, b.x].min, [a.x, b.x].max
b, t =3D [a.y, b.y].min, [a.y, b.y].max
moveto l, b
lineto r, b
lineto r, t
lineto l, t
closepath
end
page do
translate cx, cy
i =3D 0
coords.each_pair do |a, b|
path do
box a, b
gsave do
setgray Shade.mod_fetch(i +=3D 1)
fill
end
stroke
end
end
setrgbcolor 0.8, 0.4, 0
path do
moveto coords.first
angle =3D 180
coords.each_pair do |a, b|
d =3D (a + b) * 0.5
d +=3D (a - d).rot90
arcn d, (d - a).len, angle, (angle -=3D 90)
end
stroke
end
end
end
puts doc
allow inline Postscript as method calls (so not as postfix). The
output is dumped to standard output to be redirected to a .ps file,
which can be easily viewed on Mac OS X (and almost as easily on other
platforms using ghostscript).
require 'matrix'
# Class helpers
class Array
def tail
self[1..-1]
end
def mod_fetch i
self[i % size]
end
def each_pair
each_with_index do |a, i|
yield a, self[i+1] if i+1 < size
end
end
end
class Vector
def x
self[0]
end
def y
self[1]
end
def len
Math.sqrt(inner_product(self))
end
def rot90
Vector[-y, x]
end
def rot45 # cheap rotate by 45 degrees
Vector[x - y, x + y]
end
def to_s
"#{x} #{y}"
end
end
# Postscript class (what a hack!)
class PS
def initialize(&block)
@cmds =3D []
instance_eval(&block) if block
end
def push(*args, &block)
@cmds << args.join(' ')
@cmds << instance_eval(&block) if block
end
def to_s
@cmds.join("\n")
end
def page(&block)
instance_eval(&block)
push 'showpage'
end
def path(&block)
push 'newpath'
instance_eval(&block)
end
def gsave(&block)
push 'gsave'
instance_eval(&block)
push 'grestore'
end
def method_missing(name, *args)
push *args + [name]
end
end
# Constants and helper funcs for building image data
Basis =3D [Vector[1, 0], Vector[0, -1], Vector[-1, 0], Vector[0, 1]]
Shade =3D [0.3, 0.5, 0.7]
def fibo(n)
a, b =3D 1, 1
n.times { a, b =3D b, a + b }
a
end
def spiral(n)
if n.zero?
Vector[0, 0]
else
i =3D n - 1
spiral(i) + Basis.mod_fetch(i) * fibo(i)
end
end
# Build list of spiral coordinates
steps =3D (ARGV[0] || 11).to_i
coords =3D (0..steps).map { |i| spiral(i).rot45 }
# Calculate page/content dimensions, scale and center
inch =3D 72
margin =3D 0.5 * inch
pagew =3D 8.5 * inch
pageh =3D 11 * inch
contw =3D pagew - 2 * margin
conth =3D pageh - 2 * margin
xmin =3D coords.min { |a, b| a.x <=3D> b.x }.x
xmax =3D coords.max { |a, b| a.x <=3D> b.x }.x
ymin =3D coords.min { |a, b| a.y <=3D> b.y }.y
ymax =3D coords.max { |a, b| a.y <=3D> b.y }.y
scale =3D [contw / (xmax - xmin), conth / (ymax - ymin)].min
cx =3D (pagew - (xmax - xmin.abs) * scale) / 2
cy =3D (pageh - (ymax - ymin.abs) * scale) / 2
# Scale coords to fill page
coords.map! { |v| v * scale }
# Build Postscript image
doc =3D PS.new do
def box a, b
l, r =3D [a.x, b.x].min, [a.x, b.x].max
b, t =3D [a.y, b.y].min, [a.y, b.y].max
moveto l, b
lineto r, b
lineto r, t
lineto l, t
closepath
end
page do
translate cx, cy
i =3D 0
coords.each_pair do |a, b|
path do
box a, b
gsave do
setgray Shade.mod_fetch(i +=3D 1)
fill
end
stroke
end
end
setrgbcolor 0.8, 0.4, 0
path do
moveto coords.first
angle =3D 180
coords.each_pair do |a, b|
d =3D (a + b) * 0.5
d +=3D (a - d).rot90
arcn d, (d - a).len, angle, (angle -=3D 90)
end
stroke
end
end
end
puts doc