Skip to content

Instantly share code, notes, and snippets.

@rgchris rgchris/svg.red
Last active Sep 14, 2019

Embed
What would you like to do?
Red SVG loader/converter
Red [
Title: "SVG Tools"
Date: 13-Sep-2019
Author: "Christopher Ross-Gill"
Rights: http://opensource.org/licenses/Apache-2.0
Version: 0.2.2
History: [
0.2.2 13-Sep-2019 "Some functions for manipulating paths; refactoring"
0.2.1 26-Aug-2019 "Set Stroke/Fill off by default; handle numbers with units; open paths"
0.2.0 25-Aug-2019 "Text support in TO-DRAW"
0.1.0 23-Dec-2018 "Rudimentary Shape Support"
]
]
do either exists? %altxml.red [
%altxml.red
][
https://raw.githubusercontent.com/rgchris/Scripts/master/experimental/altxml.red
]
neaten: func [
block [block!] /pairs /flat /words
][
either words [
forall block [
new-line block to logic! all [
any-word? block/1
not find [off] block/1
]
]
][
new-line/all/skip block not flat either pairs [2] [1]
]
head block
]
svg: make object! [
adjust-number: func [
value [integer! float! percent!]
][
switch type?/word value [
float! percent! [round/to value 0.001]
integer! [value]
]
]
; quickie number parsing
number*: charset "-.0123456789eE"
name*: complement charset {^-^/ "',;}
space*: charset "^-^/ "
unit-to-number: func [
number [string!]
/local value unit
][
if parse number [
copy value [some number*]
copy unit opt [
"%" | "px" | "mm" | "cm" | "in" | "pt"
]
][
value: to either find value "." [float!][integer!] value
switch unit [
"" "px" [adjust-number value]
"%" [1% * value]
; "mm" "cm" "in" "pt" [none] ; not yet supported
]
]
]
is-value-from: func [
value [string!]
list [block!]
][
if find list value [load value]
]
path-to-block: func [
path [string!]
/local part
][
if path: parse/case path [
collect any [
#" " | #"," | #"^/"
| #"A" keep ('arc)
| #"C" keep ('curve)
| #"H" keep ('hline)
| #"L" keep ('line)
| #"M" keep ('move)
| #"Q" keep ('qcurve)
| #"S" keep ('curv)
| #"T" keep ('qcurv)
| #"V" keep ('vline)
| #"Z" keep ('close)
| #"a" keep (quote 'arc)
| #"c" keep (quote 'curve)
| #"h" keep (quote 'hline)
| #"l" keep (quote 'line)
| #"m" keep (quote 'move)
| #"q" keep (quote 'qcurve)
| #"s" keep (quote 'curv)
| #"t" keep (quote 'qcurv)
| #"v" keep (quote 'vline)
| #"z" keep ('close)
| copy part some number* keep (
adjust-number load part
)
]
][
neaten/words path
]
]
style-to-map: func [
style [string!]
/local key value
][
; quickie parsing for now
style: split style charset ":;"
if even? index? tail style [
remove back tail style
]
make map! collect [
foreach [key value] style [
keep to word! trim/head/tail key
keep trim/head/tail value
]
]
]
parse-transformation: func [
transformation [string!]
/local type part value
][
transformation: if parse transformation [
copy type [
"matrix" | "rotate" | "scale" | "translate" | "skew" | "clip"
]
"(" copy part to ")" skip
][
collect [
keep to word! type
keep path-to-block part
]
]
]
parse-font-name: func [
names [string!]
/local name
][
collect [
if not parse names [
some [
[
copy name [some name* any [" " some name*]] (
name: switch/default name [
"serif" "sans-serif" "cursive" "fantasy" "monospace" [
to word! name
]
][
name
]
)
|
{"} copy name some [some name* | " " | "," | "'"] {"}
|
"'" copy name some [some name* | " " | "," | {"}] "'"
] (
keep name
)
any space ["," any space | end]
]
][
keep 'sans-serif
]
]
]
handle-attributes: func [
node [object!]
/local attribute handler value style part
][
attributes: make map! collect [
foreach attribute node/attributes [
keep attribute/name
keep/only switch/default attribute/name [
viewbox [
if all [
value: attempt [load attribute/value]
parse value [4 integer!]
][
reduce [
adjust-number value/1
adjust-number value/2
adjust-number value/3
adjust-number value/4
]
]
]
id [
to issue! attribute/value
]
class [
collect [
foreach part split value " " [
if not empty? trim/head/tail part [
attempt [keep to word! part]
]
]
]
]
fill stroke [
any [
attempt [load attribute/value]
attribute/value
]
]
d points stroke-dasharray [
path-to-block attribute/value
]
x y cx cy dx dy r width height stroke-width stroke-miterlimit font-size [
unit-to-number attribute/value
]
fill-rule clip-rule [
is-value-from attribute/value [
"nonzero" "evenodd"
]
]
stroke-linecap [
is-value-from attribute/value [
"butt" "round" "square"
]
]
stroke-linejoin [
is-value-from attribute/value [
"arcs" "bevel" "miter" "miter-clip" "round"
]
]
font-family [
parse-font-name attribute/value
]
font-style [
is-value-from attribute/value [
"normal" "italic" "oblique"
]
]
font-weight [
switch is-value-from attribute/value [
"normal" "bold" "lighter" "bolder"
"100" "200" "300" "400" "500" "600" "700" "800" "900"
][
normal 100 200 300 400 500 lighter ['normal]
bold 600 700 800 900 bolder ['bold]
]
]
transform [
parse-transformation attribute/value
]
style [
style-to-map attribute/value
]
][
any [
attempt [load attribute/value]
attribute/value
]
]
]
]
if map? attributes/style [
; Style attributes have greater precedence:
; https://www.w3.org/TR/2008/REC-CSS2-20080411/cascade.html#q12
foreach attribute words-of attributes/style [
attribute
value: attributes/style/:attribute
if value: switch/default attribute [
; this is largely a repeat of the above, need to hang out to DRY.
fill stroke [
attempt [load value]
]
stroke-dasharray [
path-to-block value
]
stroke-width stroke-miterlimit font-size [
unit-to-number value
]
fill-rule clip-rule [
is-value-from value [
"nonzero" "evenodd"
]
]
stroke-linecap [
is-value-from value [
"butt" "round" "square"
]
]
stroke-linejoin [
is-value-from value [
"arcs" "bevel" "miter" "miter-clip" "round"
]
]
font-family [
parse-font-name value
]
font-style [
is-value-from value [
"normal" "italic" "oblique"
]
]
font-weight [
switch is-value-from value [
"normal" "bold" "lighter" "bolder"
"100" "200" "300" "400" "500" "600" "700" "800" "900"
][
normal 100 200 300 400 500 lighter ['normal]
bold 600 700 800 900 bolder ['bold]
]
]
transform [
parse-transformation value
]
][
value
][
attributes/(attribute): value
]
]
]
attributes/style: none
attributes
]
handle-kid: func [
node [object!]
/local attributes kids kid
][
neaten/words collect [
keep switch/default node/name [
g ['group]
][
node/name
]
keep attributes: handle-attributes node
keep/only either empty? kids: collect [
foreach kid node/children [
switch/default kid/type [
element [
keep handle-kid kid
]
text [
keep quote 'text
keep '_
keep kid/value
]
whitespace
][
probe kid/type
]
]
][
'_
][
kids
]
]
]
load-svg: func [
svg [string! binary!]
/local kid kids desc defs
][
case/all [
binary? svg [
svg: to string! svg
]
string? svg [
svg: load-xml/dom svg
]
]
neaten/words collect [
keep 'svg
keep handle-attributes svg
keep/only either empty? kids: collect [
foreach kid svg/children [
if kid/type = 'element [
; switch/default kid/name [
; ; defs [defs: handle-defs kid]
; desc [desc: kid/text]
; ][
keep handle-kid kid
; ]
]
]
][
'_
][
kids
]
]
]
as-pair: func [
x [number!]
y [number!]
][
make pair! reduce [round/to x 1 round/to y 1]
]
adjust-font-size: func [
size [integer! float!]
][
; round/to size * 72.0 / 96 1
round/to size * 75.0 / 96 1
]
path-command: [
'move | 'line | 'hline | 'vline | 'arc | 'curve | 'qcurve | 'curv | 'qcurv | 'close
]
path-args: #(
move: [2 number!]
line: [2 number!]
hline: [number!]
vline: [number!]
arc: [7 number!]
curve: [6 number!]
qcurve: [4 number!]
curv: [4 number!]
qcurv: [2 number!]
close: []
)
current-command: none
next-path-segment: func [
path [block!]
/local args segment
][
if head? path [
current-command: none
]
case [
tail? path [none]
parse path [
[
path-command (
current-command: path/1
args: path-args/:current-command
)
|
(
args: either none? current-command [
[fail]
][
path-args/:current-command
]
)
]
copy segment args
path: to end
][
reduce [current-command segment path]
]
/else [
make error! rejoin ["Could not parse path (at #" index? path ")"]
]
]
]
path-to-draw-path: func [
path [block!]
/local
command args mark open?
][
neaten/words collect [
open?: true
if all [
set [command args mark] next-path-segment path
command = 'move
][
keep command
keep as-pair args/1 args/2
]
while [
set [command args mark] next-path-segment mark
][
keep command
switch command [
move 'move [
keep as-pair args/1 args/2
open?: true
]
line 'line qcurv 'qcurv [
keep as-pair args/1 args/2
]
hline 'hline vline 'vline [
keep args/1
]
arc 'arc [
keep as-pair args/6 args/7
keep args/1
keep args/2
keep args/3
if mark/5 = 1 [keep 'sweep]
if mark/4 = 1 [keep 'large]
]
curve 'curve [
keep as-pair args/1 args/2
keep as-pair args/3 args/4
keep as-pair args/5 args/6
]
curv 'curv qcurve 'qcurve [
keep as-pair args/1 args/2
keep as-pair args/3 args/4
]
close 'close [
open?: false
]
]
]
; prevents Red's auto-closing behaviour
if open? [
keep [
move -1x-1
]
]
]
]
numbers-to-points: func [
numbers [block!]
/local mark
][
parse numbers [
collect [
some [
mark: number! number!
keep (as-pair mark/1 mark/2)
]
]
]
]
colors: #(
none: off
transparent: off
black: #000000
navy: #000080
darkblue: #00008b
mediumblue: #0000cd
blue: #0000ff
darkgreen: #006400
green: #008000
teal: #008080
darkcyan: #008b8b
deepskyblue: #00bfff
darkturquoise: #00ced1
mediumspringgreen: #00fa9a
lime: #00ff00
springgreen: #00ff7f
cyan: #00ffff
aqua: #00ffff
midnightblue: #191970
dodgerblue: #1e90ff
lightseagreen: #20b2aa
forestgreen: #228b22
seagreen: #2e8b57
darkslategray: #2f4f4f
darkslategrey: #2f4f4f
limegreen: #32cd32
mediumseagreen: #3cb371
turquoise: #40e0d0
royalblue: #4169e1
steelblue: #4682b4
darkslateblue: #483d8b
mediumturquoise: #48d1cc
indigo: #4b0082
darkolivegreen: #556b2f
cadetblue: #5f9ea0
cornflowerblue: #6495ed
mediumaquamarine: #66cdaa
dimgrey: #696969
dimgray: #696969
slateblue: #6a5acd
olivedrab: #6b8e23
slategrey: #708090
slategray: #708090
lightslategray: #778899
lightslategrey: #778899
mediumslateblue: #7b68ee
lawngreen: #7cfc00
chartreuse: #7fff00
aquamarine: #7fffd4
maroon: #800000
purple: #800080
olive: #808000
gray: #808080
grey: #808080
skyblue: #87ceeb
lightskyblue: #87cefa
blueviolet: #8a2be2
darkred: #8b0000
darkmagenta: #8b008b
saddlebrown: #8b4513
darkseagreen: #8fbc8f
lightgreen: #90ee90
mediumpurple: #9370db
darkviolet: #9400d3
palegreen: #98fb98
darkorchid: #9932cc
yellowgreen: #9acd32
sienna: #a0522d
brown: #a52a2a
darkgray: #a9a9a9
darkgrey: #a9a9a9
lightblue: #add8e6
greenyellow: #adff2f
paleturquoise: #afeeee
lightsteelblue: #b0c4de
powderblue: #b0e0e6
firebrick: #b22222
darkgoldenrod: #b8860b
mediumorchid: #ba55d3
rosybrown: #bc8f8f
darkkhaki: #bdb76b
silver: #c0c0c0
mediumvioletred: #c71585
indianred: #cd5c5c
peru: #cd853f
chocolate: #d2691e
tan: #d2b48c
lightgray: #d3d3d3
lightgrey: #d3d3d3
thistle: #d8bfd8
orchid: #da70d6
goldenrod: #daa520
palevioletred: #db7093
crimson: #dc143c
gainsboro: #dcdcdc
plum: #dda0dd
burlywood: #deb887
lightcyan: #e0ffff
lavender: #e6e6fa
darksalmon: #e9967a
violet: #ee82ee
palegoldenrod: #eee8aa
lightcoral: #f08080
khaki: #f0e68c
aliceblue: #f0f8ff
honeydew: #f0fff0
azure: #f0ffff
sandybrown: #f4a460
wheat: #f5deb3
beige: #f5f5dc
whitesmoke: #f5f5f5
mintcream: #f5fffa
ghostwhite: #f8f8ff
salmon: #fa8072
antiquewhite: #faebd7
linen: #faf0e6
lightgoldenrodyellow: #fafad2
oldlace: #fdf5e6
red: #ff0000
fuchsia: #ff00ff
magenta: #ff00ff
deeppink: #ff1493
orangered: #ff4500
tomato: #ff6347
hotpink: #ff69b4
coral: #ff7f50
darkorange: #ff8c00
lightsalmon: #ffa07a
orange: #ffa500
lightpink: #ffb6c1
pink: #ffc0cb
gold: #ffd700
peachpuff: #ffdab9
navajowhite: #ffdead
moccasin: #ffe4b5
bisque: #ffe4c4
mistyrose: #ffe4e1
blanchedalmond: #ffebcd
papayawhip: #ffefd5
lavenderblush: #fff0f5
seashell: #fff5ee
cornsilk: #fff8dc
lemonchiffon: #fffacd
floralwhite: #fffaf0
snow: #fffafa
yellow: #ffff00
lightyellow: #ffffe0
ivory: #fffff0
white: #ffffff
)
hex: charset "0123456789abcdefABCDEF"
default-style: #(
pen: off
fill-pen: off
line-width: 0
)
facets-of: func [
facets [map!] /local facet
][
neaten/pairs collect [
foreach facet words-of facets [
if facets/:facet [
keep to word! facet
keep facets/:facet
]
]
]
]
handle-facets: func [
inherited [map! none!]
attributes [map!]
/local style font-object value facet keepers
][
style: copy either map? inherited [
inherited
][
default-style
]
style: make map! []
foreach [facet keepers][
stroke [
style/pen: case [
word? value [
any [
select colors value
'off
]
]
tuple? value [value]
issue? value [
either parse to string! value [6 hex | 3 hex][
value
][
; Pending DEFS resolution
'off
]
]
/else ['off]
]
]
fill [
style/fill-pen: case [
word? value [
any [
select colors value
'off
]
]
tuple? value [value]
issue? value [
either parse to string! value [6 hex | 3 hex][
value
][
; Pending DEFS resolution
'off
]
]
/else ['off]
]
]
stroke-width [
style/line-width: value
]
stroke-linejoin [
style/line-join: value
]
transform [
switch first value [
matrix [
style/matrix: reduce [next value]
]
translate [
style/translate: as-pair value/2 value/3
]
]
]
][
value: any [
select attributes facet
select default-style facet
]
if value keepers
]
font-object: #()
foreach [facet keepers][
font-family [
font-object/name: any [
case [
block? value [first value]
string? value [value]
word? value [
switch/default value [
serif cursive fantasy ['serif]
sans-serif ['sans-serif]
monospace ['monospace]
]
]
]
'system
]
]
font-size [
font-object/size: adjust-font-size value
]
; font-style [
;
; ]
;
; font-weight [
; case [
;
; ]
; ]
; rotate [
; font-object/angle
; ]
][
if value: select attributes facet keepers
]
if not empty? font-object [
font-object/color: style/fill-pen
style/font: make font! body-of font-object
]
style
]
text-to-draw: func [
position [pair!] inherited [map!] svg-block [block!]
/local node attributes style kids
][
neaten/words collect [
foreach [node attributes kids] svg-block [
switch/default node [
'text [
keep reduce [
'text
position - as-pair 0 inherited/font/size
kids
]
]
tspan [
case/all [
number? attributes/x [position/x: to integer! attributes/x]
number? attributes/y [position/y: to integer! attributes/y]
number? attributes/dx [position/x: position/x + to integer! attributes/dx]
number? attributes/dy [position/y: position/y + to integer! attributes/dy]
]
keep 'push
keep/only compose [
(facets-of style: handle-facets inherited attributes)
(text-to-draw position style kids)
]
]
][
probe node
]
]
]
]
to-draw: func [
svg-block [block!] "Block created from LOAD-SVG function"
/with inherited [map! none!] "Style inherited from parent"
/local node attributes style position kids
][
neaten/words collect [
keep [pen off fill-pen off]
foreach [node attributes kids] svg-block [
switch/default node [
svg [
keep compose [
(facets-of style: handle-facets none attributes)
(to-draw/with kids style)
]
]
defs [
; to follow
]
clippath [
; to follow
]
group a [
keep 'push
keep/only compose [
(facets-of style: handle-facets inherited attributes)
(to-draw/with kids style)
]
]
text [
; Here be dragons
position: 0x0
case/all [
number? attributes/x [position/x: to integer! attributes/x]
number? attributes/y [position/y: to integer! attributes/y]
number? attributes/dx [position/x: position/x + to integer! attributes/dx]
number? attributes/dy [position/y: position/y + to integer! attributes/dy]
]
keep 'push
keep/only compose [
(facets-of style: handle-facets inherited attributes)
(text-to-draw position style kids)
]
]
rect [
keep 'push
keep/only neaten/words collect [
keep facets-of handle-facets inherited attributes
keep 'box
keep as-pair attributes/x attributes/y
keep as-pair attributes/x + attributes/width attributes/y + attributes/height
; only one radius dimension allowed...
if any [
attributes/rx
attributes/ry
][
keep any [
attributes/rx
attributes/ry
]
]
]
]
circle [
keep 'push
keep/only neaten/words compose [
(facets-of handle-facets inherited attributes)
circle
(as-pair attributes/cx attributes/cy)
(attributes/r)
]
]
ellipse [
keep 'push
keep/only neaten/words compose [
(facets-of handle-facets inherited attributes)
circle
(as-pair attributes/cx attributes/cy)
(attributes/rx)
(attributes/ry)
]
]
line [
keep 'push
keep/only neaten/words compose [
(facets-of handle-facets inherited attributes)
line
(as-pair attributes/x1 attributes/y1)
(as-pair attributes/x2 attributes/y2)
]
]
polyline [
keep 'push
keep/only neaten/words compose [
(facets-of handle-facets inherited attributes)
line (numbers-to-points attributes/points)
]
]
polygon [
keep 'push
keep/only neaten/words compose [
(facets-of handle-facets inherited attributes)
polygon (numbers-to-points attributes/points)
]
]
path [
keep 'push
keep/only neaten/words compose/deep [
(facets-of handle-facets inherited attributes)
shape [(path-to-draw-path attributes/d)]
]
]
use [
keep [text 0x0 "`USE` TO FOLLOW"]
]
][
probe node
]
]
]
]
form-number: func [
number [number!]
][
switch type?/word number [
integer! percent! [form number]
float! [
number: form number
if parse number [thru ".0" end][
clear find number ".0"
]
number
]
]
]
path-to-svg-path: func [
path [block!]
/precision to [number!]
/local point last
][
rejoin collect [
foreach point path [
case [
all [
any-word? point
point == last
][
keep " "
]
switch point [
arc [keep "A"]
curve [keep "C"]
hline [keep "H"]
line [keep "L"]
move [keep "M"]
qcurve [keep "Q"]
curv [keep "S"]
qcurv [keep "T"]
vline [keep "V"]
close [keep "Z"]
'arc [keep "a"]
'curve [keep "c"]
'hline [keep "h"]
'line [keep "l"]
'move [keep "m"]
'qcurve [keep "q"]
'curv [keep "s"]
'qcurv [keep "t"]
'vline [keep "v"]
'close [keep "z"]
][
last: point
; if move/'move, switch to line/'line
]
switch point [
sweep [keep 1]
large [keep 1]
][
if last = 'value [keep ","]
last: 'value
]
precision [
if last = 'value [keep ","]
last: 'value
point: round/to point :to
keep form-number point
]
/else [
if last = 'value [keep ","]
last: 'value
keep form-number point
]
]
]
]
]
apply-matrix: func [
point [pair! block!]
matrix [block!]
][
point: collect [
switch type?/word point [
pair! [
keep to float! point/x
keep to float! point/y
]
block! [
parse point [
2 [
point: [
integer! (keep to float! point/1)
|
float! (keep point/1)
]
]
]
]
]
]
if all [
parse point [2 float!]
parse matrix [6 number!]
][
reduce [
(x * matrix/1) + (y * matrix/3) + matrix/5
(x * matrix/2) + (y * matrix/4) + matrix/6
]
]
]
to-relative-path: func [
path [block!]
/precise precision [number!]
/local command args mark origin current
][
precision: any [precision 0.001]
neaten/words collect [
if all [
set [command args mark] next-path-segment path
command = 'move
][
args: keep reduce [
command
round/to args/1 :precision
round/to args/2 :precision
]
current: copy origin: next args
]
while [
set [command args mark] next-path-segment mark
][
case [
error? command [
do :command
]
word? command [
keep to lit-word! command
; not relative, need to update points
switch command [
move [
keep reduce [
(args/1: round/to args/1 :precision) - current/1
(args/2: round/to args/2 :precision) - current/2
]
origin/1: current/1: args/1
origin/2: current/2: args/2
]
line qcurv [
keep reduce [
(args/1: round/to args/1 :precision) - current/1
(args/2: round/to args/2 :precision) - current/2
]
current/1: args/1
current/2: args/2
]
hline [
keep (args/1: round-to args/1 :precision) - current/1
current/1: args/1
]
vline [
keep (args/2: round/to args/1 :precision) - current/2
current/2: args/2
]
arc [
keep reduce [
quote 'arc
round/to args/1 :precision ; rx
round/to args/2 :precision ; ry
args/3 ; angle
args/4 ; large-arc?
args/5 ; sweep?
(args/6: round/to args/6 :precision) - current/1
(args/7: round/to args/7 :precision) - current/2
current/1: args/6
current/2: args/7
]
]
curve [
keep reduce [
(round/to args/1 :precision) - current/1
(round/to args/2 :precision) - current/2
(round/to args/3 :precision) - current/1
(round/to args/4 :precision) - current/2
(args/5: round/to args/5 :precision) - current/1
(args/6: round/to args/6 :precision) - current/2
current/1: args/5
current/2: args/6
]
]
curv qcurve [
keep reduce [
(round/to args/1 :precision) - current/1
(round/to args/2 :precision) - current/2
(args/3: round/to args/3 :precision) - current/1
(args/4: round/to args/4 :precision) - current/2
]
current/1: args/3
current/2: args/4
]
close [
current: :origin
]
]
]
lit-word? command [
keep command
; already relative, need to update 'current
switch command [
'move [
keep args: reduce [
round/to args/1 :precision
round/to args/2 :precision
]
origin/1: current/1: current/1 + args/1
origin/2: current/2: current/2 + args/2
]
'line 'qcurv [
keep args: reduce [
round/to args/1 :precision
round/to args/2 :precision
]
current/1: current/1 + args/1
current/2: current/2 + args/2
]
'hline [
keep args: round/to args/1 :precision
current/1: current/1 + args
]
'vline [
keep args: round/to args/1 :precision
current/2: current/2 + args
]
'arc [
keep args: reduce [
round/to args/1 :precision ; rx
round/to args/2 :precision ; ry
args/3 ; angle
args/4 ; large-arc?
args/5 ; sweep?
round/to args/6 :precision
round/to args/7 :precision
]
current/1: current/1 + args/6
current/2: current/2 + args/7
]
'curve [
keep args: reduce [
round/to args/1 :precision
round/to args/2 :precision
round/to args/3 :precision
round/to args/4 :precision
round/to args/5 :precision
round/to args/6 :precision
]
current/1: current/1 + args/5
current/2: current/2 + args/6
]
'curv 'qcurve [
keep args: reduce [
round/to args/1 :precision
round/to args/2 :precision
round/to args/3 :precision
round/to args/4 :precision
]
current/1: current/1 + args/3
current/2: current/2 + args/4
]
'close [
current: :origin
]
]
]
]
]
]
]
; more things to consider:
; https://stackoverflow.com/questions/5149301/baking-transforms-into-svg-path-element-commands
]
load-svg: get in svg 'load-svg
convert-svg: func [source [file!] /local target content][
target: append copy source %.red
save/header target content: load-svg read source make object! [
Title: "SVG Converter"
Date: now/date
]
content
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.