-
Notifications
You must be signed in to change notification settings - Fork 0
/
geom-svg.stanza
127 lines (104 loc) · 4.57 KB
/
geom-svg.stanza
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
;; See license.txt for details about licensing.
defpackage geom/svg :
import core
import collections
import math
import geom/vec
import geom/box
import geom/polygon
import geom/line-segment
import geom/polyline
import geom/bounded
import geom/poseable
import geom/assembly
defn do-print-svg (s:OutputStream, assembly:Assembly) :
for elt in children(assembly) do :
match(elt) :
(c:PolyLine2|Polygon|Assembly) : do-print-svg(s, xyz(mat(assembly), c))
(c) : false
defn do-print-svg (s:OutputStream, color:Vec4, points:Seqable<Vec2>, close?:True|False) :
print(s, "<path d=\"")
defn to-int-channel (c:Double) : to-int $ round(c * 255.0)
for (p in points, i in 0 to false) do :
print-all(s, [" " ("M" when i == 0 else "L") " " x(p) " " y(p)])
print(s, " %_\" fill=\"none\" stroke=\"rgb(%_, %_, %_)\" stroke-width=\"%_\"/>" %
[("Z " when close? else "") to-int-channel(x(color)), to-int-channel(y(color)), to-int-channel(z(color)), *line-width*])
defn do-print-svg (s:OutputStream, color:Vec4, contour:Contour) :
do-print-svg(s, color, vertices(contour), true)
defn do-print-svg-header (s:OutputStream, bounds:Box2, f:OutputStream -> False) :
println-all(s, ["<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"])
println-all(s, ["<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\""])
println-all(s, [" width=\"" x(dims(bounds)) "\" height=\"" y(dims(bounds)) "\" id=\"cons\">"])
f(s)
println(s, "\n</svg>")
defn do-print-svg (s:OutputStream, polygon:Polygon) :
for c in contours(polygon) do :
do-print-svg(s, color(polygon), c)
defn do-print-svg (s:OutputStream, polyline:PolyLine2) :
for line in strokes(polyline) do :
do-print-svg(s, color(polyline), line, false)
defn svg (s:OutputStream, g:Assembly|PolyLine2|Polygon, bbox:Box2) :
do-print-svg-header(s, bbox, { (
val mg = mov(xyz(-1.0 * lo(bbox)), g)
do-print-svg(s, mg) ) } )
public defn svg (g:Assembly|PolyLine2|Polygon, filename:String, bbox:Box2) :
val s = FileOutputStream(filename)
try : svg(s, g, bbox)
finally : close(s)
public defn svg (g:Assembly|PolyLine2|Polygon, filename:String, margin:Vec2) :
svg(g, filename, fatten(xy(bounds(g)), margin))
lostanza deftype svg_path :
pts : ptr<double>
npts : int
closed : byte
bounds : ptr<double>
public lostanza deftype SVGpath :
value : ptr<svg_path>
lostanza deftype svg_image :
paths : ptr<ptr<svg_path>>
npaths : int
extern memcpy: (ptr<byte>, ptr<byte>, int) -> ptr<byte>
extern malloc: (int) -> ptr<byte>
extern svg_read: (ptr<byte>, ptr<byte>, double) -> ptr<svg_image>
extern svg_image_delete: (ptr<svg_image>) -> int
public lostanza defn read-svg (filename:ref<String>, format:ref<String>, resolution:ref<Double>) -> ref<Tuple<SVGpath>> :
val image = call-c svg_read(addr!(filename.chars), addr!(format.chars), resolution.value)
val paths = Vector<SVGpath>()
for (var i:int = 0, i < image.npaths, i = i + 1) :
val path = new SVGpath{image.paths[i]}
add(paths, path)
return to-tuple(paths)
public lostanza defn num-points (path:ref<SVGpath>) -> ref<Int> :
return new Int{path.value.npts}
public lostanza defn closed? (path:ref<SVGpath>) -> ref<True|False> :
if path.value.closed :
return true
else :
return false
public lostanza defn bounds (path:ref<SVGpath>) -> ref<Box2> :
return Box2(Vec2(new Double{path.value.bounds[0]}, new Double{path.value.bounds[1]}),
Vec2(new Double{path.value.bounds[2]}, new Double{path.value.bounds[3]}))
public lostanza defn get (path:ref<SVGpath>, i:ref<Int>) -> ref<Vec2> :
return Vec2(new Double{path.value.pts[i.value * 2]}, new Double{path.value.pts[i.value * 2 + 1]})
public defn cubic-bezier (ip1:Vec2, ip2:Vec2, ip3:Vec2, ip4:Vec2, tol:Double, level:Int) -> Seq<Vec2> :
generate<Vec2> :
let loop (p1:Vec2 = ip1, p2:Vec2 = ip2, p3:Vec2 = ip3, p4:Vec2 = ip4, level:Int = level) :
if level <= 12 :
val p12 = 0.5 * (p1 + p2)
val p23 = 0.5 * (p2 + p3)
val p34 = 0.5 * (p3 + p4)
val p123 = 0.5 * (p12 + p23)
val p234 = 0.5 * (p23 + p34)
val p1234 = 0.5 * (p123 + p234)
if distance(LineSegment2(p1, p4), p1234) > (tol * tol) :
loop(p1, p12, p123, p1234, level + 1)
loop(p1234, p234, p34, p4, level + 1)
else :
yield(p4)
public defn sample (path:SVGpath, tol:Double) -> Seq<Vec2> :
val guts = for i in 0 to (num-points(path) - 1) by 3 seq-cat :
cubic-bezier(path[i], path[i + 1], path[i + 2], path[i + 3], tol, 0)
if closed?(path) :
cat-all $ [[path[0]], guts, [path[0]]]
else :
cat([path[0]], guts)