/
placeeps.jl
264 lines (246 loc) · 9.42 KB
/
placeeps.jl
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
using DataStructures: Stack
"""
placeeps(epsfile; log=false)
This function loads and interprets an EPS file previously exported by
Cairo. Commands are 'applied' to the current drawing.
The primary intention is to extract some geometry - points and curves,
etc. - from an EPS vector graphic.
!!! warning
This function reads only 'Cairo-flavoured' EPS files. Every application that
exports EPS files defines its own chosen set of PostScript functions in a
prolog. There's no standard. The 'Cairo-flavoured' EPS prolog is hard-coded into
EPS files exported from Cairo so it's predictable, but other EPS exporters
define their own flavour, defining and calling who knows what PostScript
functions. So it's unlikely that EPS files from other sources will be
successfully processed with this function.
Also note:
- ignores clipping for now; I'm not sure how the Cairo->EPS->Cairo transformations work yet
- ignores `rectfill`` commands for now - I think these are often used just for the initial BoundingBoxes/clipping, but if they're used matrix transforms they'll need interpreting somehow
- ignores blends and gradients, embedded images, embedded fonts, among many other things...
Use `log` to print the commands to the REPL as well as execute them.
## Examples
```julia
@draw begin
translate(boxtopleft())
Luxor.placeeps("/tmp/julia.eps")
end
```
You can convert an SVG file to EPS by reading the SVG into Luxor, saving as EPS, then
placing that saved EPS file onto a new drawing:
```julia
svgfile = readsvg("julia.svg")
@eps begin
placeimage(svgfile, centered = true)
end 800 500 "/tmp/julia.eps"
@draw begin
translate(boxtopleft())
placeeps("/tmp/julia.eps")
end
```
If you want to put the list of Luxor commands in a text file for pasting into another file, you could try this (Julia v.1.7 and up). Let's say you have an SVG called `julia.svg`.
```julia
# load an SVG and save as EPS
@eps begin
svgf = readsvg("julia.svg")
placeimage(svgf, centered = true)
end 500 500 "/tmp/t.eps"
# convert EPS to Luxor commands
redirect_stdio(stdout = "/tmp/output.jl") do
placeeps("/tmp/t.eps", log = true)
end
# include Luxor commands and save as a new SVG:
@svg begin
translate(boxtopleft())
include("/tmp/output.jl")
end 500 500 "/tmp/julia.svg"
```
"""
function placeeps(epsfile;
log = false)
if !isfile(epsfile)
@warn " $epsfile is not a file that I can find..."
return false
end
lines = readlines(epsfile)
prolog = false
s = Stack{Any}() # holds numbers, string commands, etc...
log && println("# start EPS import")
for i in lines
if i == "%%EndPageSetup"
prolog = false
end
if startswith(i, "%%Creator")
if !occursin("cairo", i)
@warn "This EPS file was not created with Cairo. Expect disappointment and failure in equal measure."
end
end
if startswith(i, "%!PS-Adobe-3.0")
prolog = true
end
if prolog == true
continue
end
if startswith(i, "%")
continue
end
tkns = split(i)
for tkn in tkns
if length(tkn) > 32
# long tokens are probably image or font data
# so ignore them
continue
end
# hopefully there are some numbers in this file?
n = tryparse(Float64, tkn)
if !isnothing(tryparse(Float64, tkn))
# push numbers onto stack
push!(s, parse(Float64, tkn))
elseif tkn == "m"
p1 = pop!(s)
p2 = pop!(s)
move(Point(p2, (p1)))
log && println("move(Point($(p2), $(p1)))")
elseif tkn == "l" # lineto
p1 = pop!(s)
p2 = pop!(s)
line(Point(p2, p1))
log && println("line(Point($(p2), $(p1)))")
elseif tkn == "c" # curveto
p1 = pop!(s)
p2 = pop!(s)
p3 = pop!(s)
p4 = pop!(s)
p5 = pop!(s)
p6 = pop!(s)
curve(Point(p6, p5), Point(p4, p3), Point(p2, p1))
log && println("curve(Point($(p6), $(p5)), Point($(p4), $(p3)), Point($(p2), $(p1)))")
elseif tkn == "h" # closepath
closepath()
log && println("closepath()")
elseif tkn == "S" # stroke
strokepath()
log && println("strokepath()")
elseif tkn == "f" # fill
fillpath()
log && println("fillpath()")
elseif tkn == "f*" # eofill
log && println("# eofill?")
elseif tkn == "n" # newpath
newpath()
log && println("newpath()")
elseif tkn == "g"
p1 = pop!(s)
setgray(p1)
log && println("setgray($(p1))")
elseif tkn == "rg"
p1 = pop!(s)
p2 = pop!(s)
p3 = pop!(s)
sethue(p3, p2, p1)
log && println("sethue($(p3), $(p2), $(p1))")
elseif tkn == "q" # gsave
gsave()
log && println("gsave()")
elseif tkn == "Q" # grestore
grestore()
clipreset() # does clipping get reset by grestore ?
log && println("grestore(); clipreset()")
elseif tkn == "cm" # matrix
p1 = pop!(s)
p2 = pop!(s)
p3 = pop!(s)
p4 = pop!(s)
p5 = pop!(s)
p6 = pop!(s)
m = [p6, p5, p4, p3, p2, p1]
# matrix transforms ignored")
# setmatrix([$p6, $p5, $p4, $p3, $p2, $p1])
log && println("# setmatrix([$p6, $p5, $p4, $p3, $p2, $p1])")
elseif tkn == "w" # linewidth
p1 = pop!(s)
setline(p1)
log && println("setline($(p1))")
elseif tkn == "J"
# 0 (butt caps), 1 (round caps), or 2 (extended butt caps)
p1 = convert(Int, pop!(s) + 1)
v = ["butt", "round", "square"][p1]
setlinecap("\"$(v)\"")
log && println("setlinecap(\"$(v)\")")
elseif tkn == "j"
# 0 (miter join), 1 (round join), or 2 (bevel join)
p1 = convert(Int, pop!(s) + 1)
v = ["miter", "round", "bevel"][p1]
setlinejoin("\"$(v)\"")
log && println("setlinejoin(\"$(v)\")")
elseif tkn == "[]" # tis an empty array
push!(s, "[")
push!(s, "]")
elseif tkn == "d" # dash patterns are an array of numbers
darray = Float64[]
for e in Iterators.reverse(s)
if e isa Number
push!(darray, e)
end
if e == "]"
break
end
end
setdash(darray)
elseif tkn == "W" # clip
clip()
elseif tkn == "M" # miterlimit
p1 = pop!(s)
# setmiterlimit($p1)") # is this in Luxor or Cairo yet?
log && println("# setmiterlimit($(p1))")
elseif tkn == "re" # ????
# 0 0 500 174 re W n
p1 = pop!(s)
p2 = pop!(s)
p3 = pop!(s)
p4 = pop!(s)
rect(Point(p4, p3), p2, p1, action = :path)
log && println("# re command... ")
log && println(" rect(Point($p4, $p3), $p2, $p1, action = :path)")
elseif tkn == "rectfill"
# probably used for the initial crop box, so don't draw
p1 = pop!(s)
p2 = pop!(s)
p3 = pop!(s)
p4 = pop!(s)
# rectfill
# rect(Point($(p4), $(p3)), $(p2), $(p1), action=:stroke)
log && println("# rect(Point($(p4), $(p3)), $(p2), $(p1), action=:stroke)")
elseif endswith(tkn, "]")
val = chop(tkn)
if tryparse(Float64, val) == true
push!(s, parse(Float64, val))
end
push!(s, "]")
elseif tkn == "["
push!(s, tkn)
elseif startswith(tkn, "[")
val = chop(tkn, head = 1, tail = 0)
push!(s, "[")
push!(s, parse(Float64, val))
elseif tkn == "rectclip"
p1 = pop!(s)
p2 = pop!(s)
p3 = pop!(s)
p4 = pop!(s)
# a rectclip, will be ignored for now:
# rect(Point($(p4), $(p3)), $(p2), $(p1), action=:clip)
log && println("# rectclip ignored ")
log && println("# rect(Point($(p4), $(p3)), $(p2), $(p1), action=:clip) ")
elseif tkn == "showpage" || tkn == "end"
#
elseif tkn == "cairo_image"
# AAARGH! file contains image and binary data ...
log && println("# file contains image data, ignoring... ")
else
log && println("# &tkn ")
end
end
end
log && println("# end EPS import")
return true # anything better to return?
end