sourcestd/Svg.xtl

1⍝# Svg: pictures as SVG text, written for you (a standard library, built 2⍝# into xetal). Import it with an alias of your choice: "v:" u_se< "Svg". 3⍝# An element is its SVG text; a picture is elements joined with c_at 4⍝# inside v:p_icture, which []S_HOW shows and the command line writes. 5⍝# Points are a 2-row matrix, x over y, as []P_ATH and Geometry3D's 6⍝# g:p_roject give them; y grows downward, as SVG's does. Attributes 7⍝# are text made by v:a_ttr, v:f_ill, v:s_troke and the transforms, so 8⍝# no program writes a quote mark or an angle bracket of its own. 9⍝# Names with l: are exported; names with h: are private to this file. 10 11⍝## Attributes 12 13⍝# An attribute: "id" ᵛa̲ttr "top" is id="top" (with its leading 14⍝# space, so attributes join by c_at). The value is escaped. 15⍝# >> "v:" u_se< "Svg" 16⍝# >> "id" v:a_ttr "a&b" 17⍝# id="a&amp;b" 18ˡa̲ttr ← { name value → " " c̲at name c̲at "=\"" c̲at (ˡe̲scape value) c̲at "\"" } 19 20⍝# Text with &, <, > and the quote mark escaped, as SVG needs it in 21⍝# attributes and text alike. Text with none of them is itself, found 22⍝# with one primitive test (most attribute values and titles are such, 23⍝# and a frame of the Rosetta stone escapes some hundreds of texts). 24⍝# >> "v:" u_se< "Svg" 25⍝# >> v:e_scape "a < b & c" 26⍝# a &lt; b &amp; c 27⍝# >> v:e_scape "ui-monospace, monospace" 28⍝# ui-monospace, monospace 29⍝# >> t_ally v:e_scape "" 30⍝# 0 31ˡe̲scape ← { t → 32 0 = '+ r̲/ t m̲ember? "&<>\"" ? t 33 ʰj̲oin 'ʰe̲sc m̲ap t 34} 35 36⍝# The fill attribute: a color ("#1f2937", "none", a name). 37⍝# >> "v:" u_se< "Svg" 38⍝# >> v:f_ill "none" 39⍝# fill="none" 40ˡf̲ill ← { color → "fill" ˡa̲ttr color } 41 42⍝# A color from red, green and blue levels 0 to 255: ᵛr̲gb 40 80 120 43⍝# is rgb(40,80,120); a level is rounded and kept in range. 44⍝# >> "v:" u_se< "Svg" 45⍝# >> v:r_gb 40 80.4 300 46⍝# rgb(40,80,255) 47ˡr̲gb ← { c → 48 k ← 0 m̲ax 255 m̲in f̲loor 0.5 + f̲loat c 49 "rgb(" c̲at (f̲ormat 1 s̲elect k) c̲at "," c̲at (f̲ormat 2 s̲elect k) c̲at "," c̲at (f̲ormat 3 s̲elect k) c̲at ")" 50} 51 52⍝# The stroke attributes: color ᵛs̲troke width, with round joins. 53⍝# >> "v:" u_se< "Svg" 54⍝# >> "#333" v:s_troke 1.5 55⍝# stroke="#333" stroke-width="1.5" stroke-linejoin="round" 56ˡs̲troke ← { color width → ("stroke" ˡa̲ttr color) c̲at ("stroke-width" ˡa̲ttr f̲ormat width) c̲at " stroke-linejoin=\"round\"" } 57 58⍝## Shapes and text 59 60⍝# A polygon through the points (2 rows, x over y), closed, with the 61⍝# given attributes: attrs ᵛp̲olygon points. 62⍝# >> "v:" u_se< "Svg" 63⍝# >> (v:f_ill "#ccc") v:p_olygon 2 3 r_eshape 0 10 20 0 10 0 64⍝# <polygon points="0,0 10,10 20,0" fill="#ccc"/> 65ˡp̲olygon ← { attrs points → "<polygon points=\"" c̲at (ʰp̲airs points) c̲at "\"" c̲at attrs c̲at "/>" } 66 67⍝# A polyline through the points, open: attrs ᵛp̲olyline points. 68⍝# >> "v:" u_se< "Svg" 69⍝# >> ((v:f_ill "none") c_at "#000" v:s_troke 1) v:p_olyline 2 2 r_eshape 0 10 0 10 70⍝# <polyline points="0,0 10,10" fill="none" stroke="#000" stroke-width="1" stroke-linejoin="round"/> 71ˡp̲olyline ← { attrs points → "<polyline points=\"" c̲at (ʰp̲airs points) c̲at "\"" c̲at attrs c̲at "/>" } 72 73⍝# The position attributes of text: ᵛa̲t 12 30 is x="12" y="30". 74⍝# >> "v:" u_se< "Svg" 75⍝# >> v:a_t 12 30.5 76⍝# x="12.0" y="30.5" 77ˡa̲t ← { xy → ("x" ˡa̲ttr f̲ormat 1 s̲elect xy) c̲at "y" ˡa̲ttr f̲ormat 2 s̲elect xy } 78 79⍝# Text with its attributes (v:a_t for where, and any others), escaped: 80⍝# attrs ᵛt̲ext "the text". 81⍝# >> "v:" u_se< "Svg" 82⍝# >> ((v:a_t 12 30) c_at "font-size" v:a_ttr "14") v:t_ext "a < b" 83⍝# <text x="12" y="30" font-size="14">a &lt; b</text> 84ˡt̲ext ← { attrs t → "<text" c̲at attrs c̲at ">" c̲at (ˡe̲scape t) c̲at "</text>" } 85 86⍝# A run of text in a color inside a text element: color ᵛs̲pan "text", 87⍝# the text escaped; runs join with c_at into v:m_arkup. 88⍝# >> "v:" u_se< "Svg" 89⍝# >> "#1f2937" v:s_pan "a < b" 90⍝# <tspan fill="#1f2937">a &lt; b</tspan> 91ˡs̲pan ← { color t → "<tspan" c̲at (ˡf̲ill color) c̲at ">" c̲at (ˡe̲scape t) c̲at "</tspan>" } 92 93⍝# Markup at a font size of its own inside a text element, for a line 94⍝# that must fit where a larger one would not: size ᵛs̲ized inner. 95⍝# >> "v:" u_se< "Svg" 96⍝# >> 0.05 v:s_ized "red" v:s_pan "a" 97⍝# <tspan font-size="0.05"><tspan fill="red">a</tspan></tspan> 98ˡs̲ized ← { size inner → "<tspan" c̲at ("font-size" ˡa̲ttr f̲ormat size) c̲at ">" c̲at inner c̲at "</tspan>" } 99 100⍝# Text whose content is already markup (runs from v:s_pan, or escaped 101⍝# text): attrs ᵛm̲arkup inner. 102⍝# >> "v:" u_se< "Svg" 103⍝# >> (v:a_t 1 2) v:m_arkup ("red" v:s_pan "a") c_at "blue" v:s_pan "b" 104⍝# <text x="1" y="2"><tspan fill="red">a</tspan><tspan fill="blue">b</tspan></text> 105ˡm̲arkup ← { attrs inner → "<text" c̲at attrs c̲at ">" c̲at inner c̲at "</text>" } 106 107⍝## Groups and transforms 108 109⍝# A group of elements with attributes (a transform, a clip, a fill 110⍝# for all): attrs ᵛg̲roup elements. 111⍝# >> "v:" u_se< "Svg" 112⍝# >> (v:t_ranslate 5 5) v:g_roup (v:f_ill "red") v:p_olygon 2 3 r_eshape 0 1 2 0 1 0 113⍝# <g transform="translate(5 5)"><polygon points="0,0 1,1 2,0" fill="red"/></g> 114ˡg̲roup ← { attrs elements → "<g" c̲at attrs c̲at ">" c̲at elements c̲at "</g>" } 115 116⍝# The transform attribute moving by dx and dy: ᵛt̲ranslate dx dy. 117⍝# >> "v:" u_se< "Svg" 118⍝# >> v:t_ranslate 5 -2.5 119⍝# transform="translate(5.0 -2.5)" 120ˡt̲ranslate ← { d → "transform" ˡa̲ttr "translate(" c̲at (f̲ormat 1 s̲elect d) c̲at " " c̲at (f̲ormat 2 s̲elect d) c̲at ")" } 121 122⍝# The transform attribute turning by d degrees about the origin 123⍝# (clockwise on the screen, since y grows downward). 124⍝# >> "v:" u_se< "Svg" 125⍝# >> v:r_otate 90 126⍝# transform="rotate(90)" 127ˡr̲otate ← { d → "transform" ˡa̲ttr "rotate(" c̲at (f̲ormat d) c̲at ")" } 128 129⍝# The transform attribute mapping the unit square onto a quadrilateral 130⍝# given by three of its screen corners, top left, top right and bottom 131⍝# left (an affine fit; the fourth corner follows): `v:m_atrix (2 3 132⍝# r_eshape ...)`, the corners as columns, x over y. Text drawn inside 133⍝# it at unit coordinates lands on the face. 134⍝# >> "v:" u_se< "Svg" 135⍝# >> v:m_atrix 2 3 r_eshape 10 30 10 20 20 40 136⍝# transform="matrix(20 0 0 20 10 20)" 137ˡm̲atrix ← { c → 138 tl ← 1 s̲elect₂ c 139 tr ← (2 s̲elect₂ c) − tl 140 bl ← (3 s̲elect₂ c) − tl 141 "transform" ˡa̲ttr "matrix(" c̲at (f̲ormat tr) c̲at " " c̲at (f̲ormat bl) c̲at " " c̲at (f̲ormat tl) c̲at ")" 142} 143 144⍝# The transform attribute scaling by k about the origin. 145⍝# >> "v:" u_se< "Svg" 146⍝# >> v:s_cale 2 147⍝# transform="scale(2)" 148ˡs̲cale ← { k → "transform" ˡa̲ttr "scale(" c̲at (f̲ormat k) c̲at ")" } 149 150⍝## Clips and gradients 151 152⍝# A clip region named id, a polygon through the points, to put among 153⍝# the picture's definitions; id ᵛc̲lipped elements then shows only 154⍝# what falls inside it. 155⍝# >> "v:" u_se< "Svg" 156⍝# >> "top" v:c_lip 2 3 r_eshape 0 10 20 0 10 0 157⍝# <clipPath id="top"><polygon points="0,0 10,10 20,0"/></clipPath> 158ˡc̲lip ← { id points → "<clipPath" c̲at ("id" ˡa̲ttr id) c̲at ">" c̲at ("" ˡp̲olygon points) c̲at "</clipPath>" } 159 160⍝# Elements shown only inside the clip region named id. 161⍝# >> "v:" u_se< "Svg" 162⍝# >> "top" v:c_lipped "<rect/>" 163⍝# <g clip-path="url(#top)"><rect/></g> 164ˡc̲lipped ← { id elements → ("clip-path" ˡa̲ttr "url(#" c̲at id c̲at ")") ˡg̲roup elements } 165 166⍝# A gradient named id from one color at the top to another at the 167⍝# bottom, to put among the definitions; fill with "url(#id)". 168⍝# >> "v:" u_se< "Svg" 169⍝# >> "sky" v:g_radient "#fff" "#88f" 170⍝# <linearGradient id="sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#88f"/></linearGradient> 171ˡg̲radient ← { id colors → 172 top ← d̲isclose 1 s̲elect colors 173 bottom ← d̲isclose 2 s̲elect colors 174 "<linearGradient" c̲at ("id" ˡa̲ttr id) c̲at " x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\"" c̲at ("stop-color" ˡa̲ttr top) c̲at "/><stop offset=\"1\"" c̲at ("stop-color" ˡa̲ttr bottom) c̲at "/></linearGradient>" 175} 176 177⍝## Pictures 178 179⍝# The picture: w h ᵛp̲icture elements, with the definitions (clips, 180⍝# gradients) first among the elements if any. The view is w by h with 181⍝# the origin at the top left, and the picture scales to its place. 182⍝# >> "v:" u_se< "Svg" 183⍝# >> 20 10 v:p_icture "" 184⍝# <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 10" width="20" height="10" role="img"></svg> 185ˡp̲icture ← { wh elements → 186 w ← f̲ormat 1 s̲elect wh 187 h ← f̲ormat 2 s̲elect wh 188 "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 " c̲at w c̲at " " c̲at h c̲at "\"" c̲at ("width" ˡa̲ttr w) c̲at ("height" ˡa̲ttr h) c̲at " role=\"img\">" c̲at elements c̲at "</svg>" 189} 190 191⍝# A picture whose definitions (clips, gradients) come first: `w h 192⍝# v:p_ictureWith (defs; elements) is w h v:p_icture <defs>defs</defs> 193⍝# elements`. 194⍝# >> "v:" u_se< "Svg" 195⍝# >> 2 2 v:p_ictureWith "<clipPath/>" "<rect/>" 196⍝# <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2" width="2" height="2" role="img"><defs><clipPath/></defs><rect/></svg> 197ˡp̲ictureWith ← { wh parts → wh ˡp̲icture "<defs>" c̲at (d̲isclose 1 s̲elect parts) c̲at "</defs>" c̲at d̲isclose 2 s̲elect parts } 198 199⍝## Private helpers 200 201⍝# One character escaped for SVG text and attributes. 202ʰe̲sc ← { c → c = f̲irst "&" ? "&amp;"◆ c = f̲irst "<" ? "&lt;"◆ c = f̲irst ">" ? "&gt;"◆ c = f̲irst "\"" ? "&quot;"◆ c } 203 204⍝# Boxed texts joined into one (none is the empty text). 205ʰj̲oin ← { b → 206 0 = t̲ally b ? "" 207 d̲isclose '{ x y → e̲nclose (d̲isclose x) c̲at d̲isclose y } r̲/ b 208} 209 210⍝# The columns of a 2-row matrix as "x,y x,y ...". 211ʰp̲airs ← { points → 212 xs ← 'f̲ormat m̲ap 1 s̲elect points 213 ys ← 'f̲ormat m̲ap 2 s̲elect points 214 ps ← xs '{ x y → e̲nclose (d̲isclose x) c̲at "," c̲at d̲isclose y } e̲ach ys 215 d̲isclose '{ x y → e̲nclose (d̲isclose x) c̲at " " c̲at d̲isclose y } r̲/ ps 216}