Skip to content

Commit

Permalink
eps
Browse files Browse the repository at this point in the history
  • Loading branch information
cormullion committed Sep 2, 2023
1 parent 18d96c8 commit 5e81f15
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 12 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
### Added

- `circlering()`, creates ring of circles inside a circle
- `polysuper()`, creates superellipse-basd polygons
- `polysuper()`, creates superellipse-based polygons
- `setfillrule()`, access Cairo's fill rule parameter
- `getfillrule()`
- `tidysvg(fromfile, tofile)`, munge those SVG glyphs
- dependency on DataStructures.jl added

### Changed

Expand Down
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
Rsvg = "c4c386cf-5103-5370-be45-f3a111cca3b8"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"

[compat]
Cairo = "0.7, 0.8, 1.0"
Colors = "0.9, 0.10, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 1.0"
DataStructures = "0.18"
FFMPEG = "0.4"
FileIO = "1"
Juno = "0.7, 0.8"
Expand Down
1 change: 1 addition & 0 deletions src/Luxor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ include("deprecations.jl")
include("randompoints.jl")
include("Path.jl")
include("precompile.jl")
include("placeeps.jl")
# include("play.jl") # will require MiniFB
# include("shapefile.jl") # don't load unless you've loaded Shapefile.jl

Expand Down
38 changes: 27 additions & 11 deletions src/images.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ function placeimage(img::Cairo.CairoSurface, xpos, ypos; centered = false)
w, h = img.width, img.height
xpos, ypos = xpos - (w / 2), ypos - (h / 2)
end
Cairo.set_source_surface(_get_current_cr(), img, xpos, ypos)
# no alpha
Cairo.paint(_get_current_cr())
@layer begin
Cairo.set_source_surface(_get_current_cr(), img, xpos, ypos)
# no alpha
Cairo.paint(_get_current_cr())
end
end

placeimage(img::Cairo.CairoSurface, pt::Point = O; kwargs...) = placeimage(img, pt.x, pt.y; kwargs...)
Expand All @@ -62,8 +64,10 @@ function placeimage(img::Cairo.CairoSurface, xpos, ypos, alpha; centered = false
w, h = img.width, img.height
xpos, ypos = xpos - (w / 2), ypos - (h / 2)
end
Cairo.set_source_surface(_get_current_cr(), img, xpos, ypos)
paint_with_alpha(_get_current_cr(), alpha)
@layer begin
Cairo.set_source_surface(_get_current_cr(), img, xpos, ypos)
paint_with_alpha(_get_current_cr(), alpha)
end
end

placeimage(img::Cairo.CairoSurface, pt::Point, alpha; kwargs...) =
Expand Down Expand Up @@ -173,8 +177,14 @@ function placeimage(im::SVGimage, pos = O; centered = false)
pos = pos - ((w / 2), (h / 2))
end
@layer begin
translate(pos)
Rsvg.handle_render_cairo(Luxor._get_current_cr(), im.im)
@layer begin
translate(pos)
@layer begin
# hack - do these extra saves prevent the UTM breakage? (#267)
# no, I think they don't
Rsvg.handle_render_cairo(Luxor._get_current_cr(), im.im)
end
end
end
end

Expand Down Expand Up @@ -212,8 +222,10 @@ function placeimage(buffer::AbstractMatrix{UInt32}, pt = O;
w, h = size(buffer)
pt = Point(pt.x - (w / 2), pt.y - (h / 2))
end
Cairo.set_source_surface(Luxor._get_current_cr(), Cairo.CairoImageSurface(buffer, Cairo.FORMAT_ARGB32), pt.x, pt.y)
paint_with_alpha(_get_current_cr(), alpha)
@layer begin
Cairo.set_source_surface(Luxor._get_current_cr(), Cairo.CairoImageSurface(buffer, Cairo.FORMAT_ARGB32), pt.x, pt.y)
paint_with_alpha(_get_current_cr(), alpha)
end
end

"""
Expand All @@ -228,15 +240,19 @@ at the position.
"""
function placeimage(buffer::AbstractMatrix{ARGB32}, args...; kargs...)
# premultiply alpha
placeimage(reinterpret(Luxor.Cairo.UInt32, _to_uint32_color.(buffer)), args...; kargs...)
@layer begin
placeimage(reinterpret(Luxor.Cairo.UInt32, _to_uint32_color.(buffer)), args...; kargs...)
end
end

placeimage(buffer::AbstractMatrix{<:Colorant}, args...; kargs...) = placeimage(convert.(ARGB32, buffer), args...; kargs...)

function placeimage(buffer::Drawing, args...;
kargs...)
if buffer.surfacetype == :svg
placeimage(_readsvgstring(String(copy(buffer.bufferdata))), args...; kargs...)
@layer begin
placeimage(_readsvgstring(String(copy(buffer.bufferdata))), args...; kargs...)
end
else
throw(error("surfacetype `$(buffer.surfacetype)` is not supported. Use `image` instead."))
end
Expand Down
235 changes: 235 additions & 0 deletions src/placeeps.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
using DataStructures: Stack

"""
Luxor.placeeps(epsfile; log=false)
Currently not exported or supported.
This function loads and interprets an EPS file previously exported by
Cairo. This is intended mainly to get the geometry - the points and curves,
etc. - from a file. It ignores quite a few EPS features...
- it reads only Cairo-written EPS files. Each EPS file defines its own
set of PostScript functions in a prolog. The Cairo EPS prolog is hard-coded into EPS files
created by Cairo so it's fairly predictable, other EPS files define their own prologs, so who
knows what PostScript functions are called...
- 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
- ignores blends, gradients, images, fonts, among many other things...
Use `log` to print the Luxor commands 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())
Luxor.placeeps("/tmp/julia.eps")
end
```
"""
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 "EPS file was not created with Cairo. Expect failure."
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 we'll ignore it
continue
end
# hopefully there are some numbers in this file
n = tryparse(Float64, tkn)
if !isnothing(tryparse(Float64, tkn))
# push any 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)
sym = [:butt, :round, :square][p1]
setlinecap(Symbol(sym))
log && println("setlinecap(Symbol($sym))")
elseif tkn == "j"
# 0 (miter join), 1 (round join), or 2 (bevel join)
p1 = convert(Int, pop!(s) + 1)
sym = [:miter, :round, :bevel][p1]
setlinejoin(Symbol(sym))
elseif tkn == "[]" # empty array
push!(s, "[")
push!(s, "]")
elseif tkn == "d" # dash, 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 Cairo?
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)
# this rectclip ignored:
# 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?
end

0 comments on commit 5e81f15

Please sign in to comment.