This is the seventh time that I have implemented a library of useful functions. The motivation this time was to get a more robust and simpler solution based on:
Most operations on SVG paths are straightforward. The one exception is reversing a path. The original SVG path description was poorly defined for this operation. It was not always clear when a new subpath started, nor was it clear that reversing a path twice would have the same subpath structure as the original path. The aim in the new implementation is therefore to have a subpath structure that makes reversing a path just the operation of reversing the order of the subpaths and reversing the drawing commands of each subpath.
Animating a path is still poorly described in SVG 1.2. To make life easier the internal form should require:
The proposed internal form is therefore:
Reversing a subpath consists of:
Suppose we have the following path:
<path d=" M xa,ya C xb,yb xc,yc xd,yd Z M xh,yh L xi,yi M xj,yj L xk,yk "/>
We need an internal representation that allows us to change relative to absolute and vice versa and to reverse the path quickly and easily. This requires the individual path elements to be self-sufficient. To change to relative produces:
<path d=" M xa,ya c xb-xa,yb-ya xc-xa,yc-ya xd-xa,yd-ya Lxa-xd,ya-yd (inserted possibly) Z m xh-xa,yh-ya or Mxh,yh (the second alternative is necessary as Chrome currently has a bug withrespect to Zm) l xi-xh,yi-yh m xj-xi,yj-yi or M xj,yj l xk-xj,yk-yj "/>
Reversing the path produces:
<path d=" Mxk,yk Lxj,yj Mxi,yi Lxh,yh Mxa,ya Lxd,yd Cxc,yc xb,yb xa,ya Z "/>
Internal forms for the line drawing commands are reasonably obvious:
<c t="C" o="C" cx="xa" cy="ya" x1="xb" y1="yb" x2="xc" y2="yc" x3="xd" y3="yd"/> <c t="L" o="L" cx="xi" cy="xj" x3="yi" y3="yj"/>
Just adding the initial position makes both reversing and changing to relative straightforward. The attribute t defines the type and o defines the original type. All abbreviations are changed to one or other of these on input (l,v,h,V,H all become L) (S,Q,T,c,s,q,t all become C).
The SVG document states that each path consists of a set of subpaths which are either open or closed. The Z command effectively completes one subpath and starts a new one. The meaning of the two being different in terms of linecap etc. This suggests that an internal form should consist of a set of subpaths (M to Z) or (M to C/L). As the Z is effectively adding an additional L command, the two would be closer in form if M to Z added the closing line and M to C/L added an end curve command. Also the Z should add an M command after it if the next command is a line drawing command.Curves would either be M...LZ or M...E. On reversing, Z and E effectively become M and M becomes either Z or E. That would imply that either the internal form should differentiate between the two type of M or the subpaths should be named differently. I prefer the first approach as it keeps the individual elements with all the information required. If an M is turned to relative it must retain the previous origin so each M element must contain two coordinates, the previous current position and the new one. Similarly, as the E becomes a C, it must retain the next current position. The same is true for Z. The forms for the four commands should be:
<c t="M" o="M" cx="xbf" cy="ybf" x3="xa" y3="ya"/> <c t="N" o="M" cx="xbf" cy="ybf" x3="xa" y3="ya"/> <c t="Z" o="Z" cx="xk" cy="yk" x1="xi" y1="yi"/> <c t="E" o="" cx="xk" cy="yk" x1="xi" y1="yi"/>
Suppose we have the following SVG document:
<svg width="1000" height="1000"> <style type="text/css"> path {stroke:blue;stroke-width:2;fill:none} </style> <path style="stroke:blue" id="example" d="M100,250 L125,275 H150V250 M150,250 l25,25h25v-25C215,275 235,225 250,250S285,225 300,250Q325,275 350,250 T400,250A20,20,1,0,0,450,250a30,30,1,0,0,50,0 c15,25 35-25 50,0s35-25 50,0s35,25 50,0q25,25 50,0t50,0t50,0 v100h-650z l100,-100h300z m500,-100v40h50 m25,0 m25,0 v50h40z h50 "> <set attributeName="id" to="'example'" begin="1s" /> </path> </svg>
The internal form for the path command would be:
<path style="stroke:blue" id="example"> <s> <c t="N" o="M" cx="0" cy="0" x3="100" y3="250"/> <c t="L" o="L" cx="100" cy="250" x3="125" y3="275"/> <c t="L" o="H" cx="125" cy="275" x3="150" y3="275"/> <c t="L" o="V" cx="150" cy="275" x3="150" y3="250"/> <c t="E" o="" cx="150" cy="250" x1="150" y1="250"/> </s> <s> <c t="M" o="M" cx="150" cy="250" x3="150" y3="250"/> <c t="L" o="l" cx="150" cy="250" x3="175" y3="275"/> <c t="L" o="h" cx="175" cy="275" x3="200" y3="275"/> <c t="L" o="v" cx="200" cy="275" x3="200" y3="250"/> <c t="C" o="C" cx="200" cy="250" x1="215" y1="275" x2="235" y2="225" x3="250" y3="250"/> <c t="C" o="S" cx="250" cy="250" x1="265" y1="275" x2="285" y2="225" x3="300" y3="250"/> <c t="C" o="Q" cx="300" cy="250" x1="316.667" y1="266.667" x2="333.333" y2="266.667" x3="350" y3="250"/> <c t="C" o="T" cx="350" cy="250" x1="366.667" y1="233.333" x2="383.333" y2="233.333" x3="400" y3="250"/> <c t="A" o="A" cx="400" cy="250" a="20" b="20" d="1" e="0" f="0" x3="450" y3="250"/> <c t="A" o="a" cx="450" cy="250" a="30" b="30" d="1" e="0" f="0" x3="500" y3="250"/> <c t="C" o="c" cx="500" cy="250" x1="515" y1="275" x2="535" y2="225" x3="550" y3="250"/> <c t="C" o="s" cx="550" cy="250" x1="565" y1="275" x2="585" y2="225" x3="600" y3="250"/> <c t="C" o="s" cx="600" cy="250" x1="615" y1="275" x2="635" y2="275" x3="650" y3="250"/> <c t="C" o="q" cx="650" cy="250" x1="666.667" y1="266.667" x2="683.333" y2="266.667" x3="700" y3="250"/> <c t="C" o="t" cx="700" cy="250" x1="716.667" y1="233.333" x2="733.333" y2="233.333" x3="750" y3="250"/> <c t="C" o="t" cx="750" cy="250" x1="766.667" y1="266.667" x2="783.333" y2="266.667" x3="800" y3="250"/> <c t="L" o="v" cx="800" cy="250" x3="800" y3="350"/> <c t="L" o="h" cx="800" cy="350" x3="150" y3="350"/> <c t="L" o="" cx="150" cy="350" x3="150" y3="250"/> <c t="Z" o="Z" cx="150" cy="250" x1="150" y1="250"/> </s>
<s> <c t="M" o="" cx="150" cy="250" x3="150" y3="250"/> <c t="L" o="l" cx="150" cy="250" x3="250" y3="150"/> <c t="L" o="h" cx="250" cy="150" x3="550" y3="150"/> <c t="L" o="" cx="550" cy="150" x3="150" y3="250"/> <c t="Z" o="Z" cx="150" cy="250" x1="650" y1="150" /> </s> <s> <c t="N" o="m" cx="150" cy="250" x3="650" y3="150"/> <c t="L" o="v" cx="650" cy="150" x3="650" y3="190"/> <c t="L" o="h" cx="650" cy="190" x3="700" y3="190"/> <c t="E" o="" cx="700" cy="190" x1="725" y1="190" /> </s> <s> <c t="N" o="m" cx="700" cy="190" x3="725" y3="190"/> <c t="E" o="" cx="725" cy="190" x1="750" y1="190"/> </s> <s> <c t="M" o="m" cx="725" cy="190" x3="750" y3="190"/> <c t="L" o="v" cx="750" cy="190" x3="750" y3="240"/> <c t="L" o="h" cx="750" cy="240" x3="790" y3="240"/> <c t="L" o="" cx="790" cy="240" x3="750" y3="190"/> <c t="Z" o="Z" cx="750" cy="190" x1="750" y1="190"/> </s> <s> <c t="N" o="" cx="750" cy="190" x3="750" y3="190"/> <c t="L" o="h" cx="750" cy="190" x3="800" y3="190"/> <c t="E" o="" cx="800" cy="190" x1="0" y1="0"/> </s> </path>
The basic architecture for the 2D functions is shown below:
Path_ology Basic Architecture
The conversion to SVG takes place in four steps:
This transformation takes the full path description and turns it into XML, one element per command. A typical output might be:
<path style="stroke:blue" id="example"> <M o="M" x="100" y="250" /> <L o="L" x="125" y="275" /> <H o="H" x="150" /> <V o="V" y="250" /> <M o="M" x="150" y="250" /> <l o="l" x="25" y="25" /> <h o="h" x="25" /> <v o="v" y="-25" /> <C o="C" r="215" u="275" v="235" w="225" x="250" y="250" /> <S o="S" v="285" w="225" x="300" y="250" /> <Q o="Q" v="325" w="275" x="350" y="250" /> <T o="T" x="400" y="250" /> <A o="A" g="20" b="20" d="1" e="0" f="0" x="450" y="250" /> <a o="a" g="30" b="30" d="1" e="0" f="0" x="50" y="0" /> <content> <set attributeName="id" to="'example'" begin="1s" /> </content> </path>
A major problem is handling what can be a very long d attribute (this can be over 1 Mbyte). To avoid very large internal variables, the string being worked on is always a small part of the total string. The next command's maximum length can be estimated given that the number of arguments to the command are known. Currently numbers with exponents are not catered for but it would be relatively easy to change if the need was there. The command recogniser is a finite state table that is optimised for numbers having several digits either before or after the decomal point. It runs significantly faster than a version based on regular expressions.
Quite a straightforward recursive template that keeps a record of the last current position and possible Q and C control points. It uses this information to transform the next command into either an absolute cubic or a line. The appearance of a Z command is always preceded by an L command back to the initial point of the closed path. Each unclosed path is terminated by an E element and each Z followed by a drawing command has an M element inserted. The internal form of the commands is established here. Two attributes t and o give the internal type and the original type of the command. Each command includes attributes cx,cythat give the current position. The attributes x3,y3 always contain the end point that will be the next current position. The cubic command has additional attributes x1,y1,x2,y2. The arc command has additional attributes a,b,d,e,f. The attribute names are either one or two characters long to keep the file size down and each arithmetic computation rounds the number to 3 decimal places (this can be changed by the user). A typical output file might be:
<path style="stroke:blue" id="example"> <c t="M" o="M" cx="0" cy="0" x3="100" y3="250" /> <c t="L" o="L" cx="100" cy="250" x3="125" y3="275" /> <c t="L" o="H" cx="125" cy="275" x3="150" y3="275" /> <c t="L" o="V" cx="150" cy="275" x3="150" y3="250" /> <c t="E" o="" cx="150" cy="250" /> <c t="M" o="M" cx="150" cy="250" x3="150" y3="250" /> <c t="L" o="l" cx="150" cy="250" x3="175" y3="275" /> <c t="L" o="h" cx="175" cy="275" x3="200" y3="275" /> <c t="L" o="v" cx="200" cy="275" x3="200" y3="250" /> <c t="C" o="C" cx="200" cy="250" x1="215" y1="275" x2="235" y2="225" x3="250" y3="250" /> <c t="C" o="S" cx="250" cy="250" x1="265" y1="275" x2="285" y2="225" x3="300" y3="250" /> <c t="C" o="Q" cx="300" cy="250" x1="316.667" y1="266.667" x2="333.333" y2="266.667" x3="350" y3="250" /> <c t="C" o="T" cx="350" cy="250" x1="366.667" y1="233.333" x2="383.333" y2="233.333" x3="400" y3="250" /> <c t="A" o="A" cx="400" cy="250" a="20" b="20" d="1" e="0" f="0" x3="450" y3="250" /> <c t="A" o="a" cx="450" cy="250" a="30" b="30" d="1" e="0" f="0" x3="500" y3="250" /> .... </path>
A simple transformation that inserts a subpath element s around each group starting with a command of type M. This is an XSLT2 transformation using the for-each-group function.
Typical output would be:<path style="stroke:blue" id="example"> <s> <c t="M" o="M" cx="0" cy="0" x3="100" y3="250" /> <c t="L" o="L" cx="100" cy="250" x3="125" y3="275" /> <c t="L" o="H" cx="125" cy="275" x3="150" y3="275" /> <c t="L" o="V" cx="150" cy="275" x3="150" y3="250" /> <c t="E" o="" cx="150" cy="250" /> </s> <s> <c t="M" o="" cx="150" cy="250" x3="150" y3="250" /> <c t="L" o="l" cx="150" cy="250" x3="250" y3="150" /> <c t="L" o="h" cx="250" cy="150" x3="550" y3="150" /> <c t="L" o="" cx="550" cy="150" x3="150" y3="250" /> <c t="Z" o="Z" cx="150" cy="250" /> </s> </path>
Finally, this transformation makes the individual subpaths self-sufficient by changing M commands to N commands if the command starts an open subpath. The closing E command adds the current position of the next curve as x1,y1 attributes. This facilitates a straightforward function to do path reversal. The final form is something like:
<path style="stroke:blue" id="example"> <s> <c t="N" o="M" cx="0" cy="0" x3="101" y3="151" /> <c t="L" o="h" cx="101" cy="151" x3="251" y3="151" /> <c t="L" o="v" cx="251" cy="151" x3="251" y3="301" /> <c t="L" o="h" cx="251" cy="301" x3="101" y3="301" /> <c t="E" o="" cx="101" cy="301" x1="302" y1="152" /> </s> <s> <c t="N" o="M" cx="101" cy="301" x3="302" y3="152" /> <c t="L" o="h" cx="302" cy="152" x3="452" y3="152" /> <c t="L" o="v" cx="452" cy="152" x3="452" y3="302" /> <c t="L" o="h" cx="452" cy="302" x3="302" y3="302" /> <c t="E" o="" cx="302" cy="302" x1="103" y1="353" /> </s> <s> <c t="M" o="M" cx="302" cy="302" x3="103" y3="353" /> <c t="L" o="h" cx="103" cy="353" x3="253" y3="353" /> <c t="L" o="v" cx="253" cy="353" x3="253" y3="503" /> <c t="L" o="h" cx="253" cy="503" x3="103" y3="503" /> <c t="L" o="" cx="103" cy="503" x3="103" y3="353" /> <c t="Z" o="Z" cx="103" cy="353" x1="304" y1="354" /> </s> <s> <c t="M" o="M" cx="103" cy="353" x3="304" y3="354" /> <c t="L" o="h" cx="304" cy="354" x3="454" y3="354" /> <c t="L" o="v" cx="454" cy="354" x3="454" y3="504" /> <c t="L" o="h" cx="454" cy="504" x3="304" y3="504" /> <c t="L" o="" cx="304" cy="504" x3="304" y3="354" /> <c t="Z" o="Z" cx="304" cy="354" x1="505" y1="155" /> </s> <s> <c t="N" o="M" cx="304" cy="354" x3="505" y3="155" /> <c t="L" o="h" cx="505" cy="155" x3="655" y3="155" /> <c t="L" o="v" cx="655" cy="155" x3="655" y3="305" /> <c t="L" o="h" cx="655" cy="305" x3="505" y3="305" /> <c t="E" o="" cx="505" cy="305" x1="0" y1="0" /> </s> </path>
There are two transformations that change the SVG internal form back to the d attribute:
This is similar to back_to_svg except that it tries to simplify both L and C commands. If possible C commands are turned to S, Q or T commands while L commands are turned to H and V. A parameter toline if set to ctol will also attempt to convert C to L, H or V if appropriate. A typical bat file might be:
java -jar saxon.jar -o inner.xml simpilify_and_back_to_svg.xsl mode=relMabs toline=ctol
The library contains a number of transformations that take the internal form of a set of paths and converts them to a transformed version of the internal form. these are:
This takes each subpath and reverses it. An example is shown below:
Reversing a path
For an object made up of a series of short lines, this transformation takes any short line, which has lines before and after it, and converts it to a cubic Bezier with the tangent at the start of the curve equal to the direction of the first line and similarly the end tangent the same as the direction of the third line. The parameter short defines the maximum length of the line which is converted.
The heuristic is due to Maxim Shemanarev http://www.antigrain.com/agg_research/bezier_interpolation.html.
Smooth Short Lines
Transforms each arc command to between 1 and 4 cubic commands depending on the length of the arc. Conversion from arc to C is complicated by the SVG format for arcs where the arc radii effectively define the aspect ratio of the ellipse and, if the radii are too small to define a curve from the start position to the end, it is scaled until a solution is available. For arcs greater than 90 degrees, it is sensible to break the arc into sections. A maximum of 4 cubics is needed. The papers by Maisonobe (Drawing an elliptical arc using polylines, quadratic or cubic Bezier curves. July, 2003 (http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf)) and Hoffman (Bezier Curves, (http://www.fho-emden.de/~hoffmann/bezier18122002.pdf)) give ways in which this can be tackled. Essentially the tangent at the two end points is known and it is a matter of positioning the two control points at places that yield the minimum error between the cubic and the arc. There is not a unique solution.
Arc Conversion
A transformation, def_perspective_scenes.xsl, has been implemented that takes some existing SVG paths, adds a Z=0 to each path coordinate and then allows transformations to be applied to the 3D objects before performing a perspective transformation back to a 2D path. The file to be transformed should be a regular SVG file with the following format:
<svg ...> Any SVG commands <metadata> <scenes> <timing>6s</timing> <objbg transform="translate(0,0,-300)"> <obj class="w18mt01" href="w18mt01"/> ..... </objbg> <objbg transform="rotx(90)"> <obj class="w18fg01" href="w18fg01"/> .... </objbg> <scene id="w18st1"> <perspective> <over>-20</over> <up>-11</up> <rad>1600</rad> <pp>1800</pp> <newx>0</newx> <newy>0</newy> <scale>1</scale> <spline>constant</spline> </perspective> <objfg transform="translate(-145,-64,80) roty(45)"> <obj class="w18wdm1" href="w18wdm1"/> <obj class="w18wdm1l" href="w18wdm1l" /> <obj class="w18wdm1r" href="w18wdm1r" /> <obj class="w18wdm2" href="w18wdm2" /> <obj class="w18wdm3" href="w18wdm3" transform="rotz(60)" /> <obj class="w18wdm4" href="w18wdm4" transform="rotz(60)" /> </objfg> </scene> <scene id="w18st2"> ...... </scene> ...... </scenes> </metadata> <defs> <path id="w18mt01" d="M-20,10...."/> <path id="w18fg01" d="M-40,30...."/> <path id="w18wdm1" d="M-25,60C-8.5,65,8.3,65,25,60..."/> <path id="w18wdm1r" d="M25,60C25,60,25,20,25,20..."/> <path id="w18wdm3" d="M1.3,1.3L2.5,0L32.5,30..."/> <path id="w18wdm4" d="M5,5L10,0..."/> </defs> More SVG </svg>
The pseudo SVG is transformed by a set of command as follows:
java -jar saxon.jar -o t1.xml file.svg break_path.xsl java -jar saxon.jar -o t2.xml t1.xml make_abs.xsl java -jar saxon9.jar t2.xml add_subpath.xsl >t3.xml java -jar saxon.jar -o t4.xml t3.xml make_xml.xsl java -jar saxon9.jar t4.xml def_perspective_scenes.xsl >/t5.xml java -jar saxon.jar -o t6.xml t5.xml change_accuracy.xsl decplaces=1 java -jar saxon.jar -o out.svg t6.xml back_to_svg.xsl mode=relMabs java -jar saxon.jar -o anim.svg out.svg animate_scenes.xsl
The first four commands convert the individual path descriptions in the defs section to the internal XML form.
The individual scene definitions use these path descriptions to construct the individual key frames of an animation. Constant background material that applies to all scenes is given by a set of objbg elements. In the example there are two separate backgrounds. The first is placed at the back of the scene with a far-away Z value. The second is an X-Y scene that is changed to an X-Z scene that forms the ground layout for the animation.
Each scene consists of a set of foreground objects defined as objfg elements. Note that both the objbg and objfg elements can have a 3D transformation applied to the individual obj elements and to the background or foreground element itself. In the foreground object example, a rotation about the Y-axis followed by a translation is applied to the whole object and two of the individual paths making up the object have a transformation about the Z-axis as well.
The perspective transformation is defined by the perspective element and this also defines any post processing to be applied after the conversion back to 2D. The 3D axes have the Y-axis downwards as shown below.
SVG 3D Axes
The perspective transformation is defined by the parameters in the following diagram:
Perspective Transformation
A viewing point of this scene is defined in spherical coordinates by the elements over, up and rad. Both over and up are given in degrees and rad is the distance from the origin. A plane can be imagined perpendicular to the line from the viewing point to the origin. This plane is called the Picture Plane. The element pp defines the distance from the viewing point to the picture plane. The scene to be viewed normally lies on the opposite side of this plane. Visual rays from the objects to the viewing point pierce the picture plane, tracing out a perspective drawing as it scans the scene. It is this projected view that is converted back to 2D. If the picture plane is moved towards the viewer, the image becomes proportionately smaller, and vice versa. The coordinates of the viewing point can be located in any of the eight octants, so that the figure can be observed from any possible position.
Once the perspective view has been calculated, the size of the view and the origin can be changed by the parameters newx,newy and size.
Definitions of the individual matrices used in the transformations are given in Appendix B.
The output from the transformation def_perspective_scenes.xsl consists of a set of scene definitions overlayed on top of each other, which is not particularly useful unless only a single scene is defined. However it is the basis for generating an animation using the individual scenes as the key frames. Usually it is worth reducing the accuracy before converting the XML back to SVG.
The output will have the form:
<svg ...> Original SVG commands in file <g id="scenes"> <metadata> <timing>6s</timing> </metadata> <g id="scene1" title="constant"> <path id="w18strt1p1" class="w18mt01" d="M..."/> ..... <path id="w18strt1p6" class="w18fg01" d="M..."/> ..... <path id="w18strt1p24" class="w18wdm1" d="M..."/> <path id="w18strt1p25" class="w18wdm1r" d="M..."/> <path id="w18strt1p26" class="w18wdm3" d="M..."/> <path id="w18strt1p27" class="w18wdm4" d="M..."/> </g> <g id="scene2" title="constant"> <path id="w18strt1p1" class="w18mt01" d="M..."/> ..... <path id="w18strt1p6" class="w18fg01" d="M..."/> ..... <path id="w18strt1p24" class="w18wdm1" d="M..."/> <path id="w18strt1p25" class="w18wdm1r" d="M..."/> <path id="w18strt1p26" class="w18wdm3" d="M..."/> <path id="w18strt1p27" class="w18wdm4" d="M..."/> </g> </g> Any SVG commands in the original </svg>
The original SVG context from the original is retained. The individual scenes have a title attribute that is used to define the spline effect required in the animation between this frame and the next. Each path has a unique id added that is used by the animation transformation. The metadata associated with the g element with id attribute set to scenes defines the duration of the animation. The animate_scenes transformation is straightforward. It takes each path defined in the first scene and animates the d attribute. For example:
<path id="w18strt1p1" class="w18mt01" d="M-3211.2,-661.3l251.2,775.9l4215.1,-244.8l48.2,-360.7l-4514.5,-170.4Z"/> <animate attributeName="d" calcMode="spline" values=" M-3211.2,-661.3l251.2,775.9l4215.1,-244.8l48.2,-360.7l-4514.5,-170.4Z; M-3217.3,-658.4l258.3,776l4214.6,-251.4l49.6,-361.1l-4522.5,-163.5Z; ... " fill="freeze" begin="w18strt0.end" dur="6s" xlink:href="#w18strt1p1" keySplines="0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1"/>
Any 3D scene can be enhanced by adding shadows of objects on the Y=0 plane. The transformation add_drop_shadow.xsl is performed before def_perspective_scenes.xsl. The scene definition is enhanced by an additional shadow element:
<shadow><over>50</over><up>-40</up></shadow> <scene id="..">
The shadow element defines the position of the sun. Unless otherwise specified, every object defined in the scene has an additional object added which is the shadow. The class of this object is the same as the original object but with drop added before. The user needs to add another style rule indicating how the shadow is to be rendered. For example:
path.dropxxx {fill:#333333;stroke:none;fill-opacity:0.5}
Making the shadow semi-transparent allows any objects underneath it to be visible.
For complex objects where only some contribute to the shadow, it is possible to improve the efficiency by not rendering the shadow of those objects. An additional attribute is added to the object:
<obj class="xxx" drop="no" href="yyy" />
Two methods of defining 3D paths have been added for convenience.
<path3d id="abc" d="M25,60,0L47,38,5"/> <p_path3d id="w18wdm1"> <M p="pb"/> <C c1="ph" c2="pm" p="pe" /> <L p="pb"/> <Z /> </p_path3d> <point id="pa" x="0" y="0" z="0"/> <point id="pb" x="-25" y="60" z="0"/> <point id="pc" x="-25" y="20" z="0"/>
The element path3d is quite similar to path but has a z parameter for each point. Only M, Z, C, L, m, z, c, l commands are allowed.
The element p_path3d uses a set of point elements that define positions and constructs 3d paths from them. Only M, L, C and Z are defined. Mostly useful for defining structures where the number of points is small but many faces exist using those points. A box would be a good example.
The first stage of converting the d attribute to XML is the most demanding because:
The d attribute initially has spaces changed to commas and a translate function checks whether the next character is a command letter or a number. The approach is then to output the command element for each command letter and call a template to capture the set of numbers required. The number recogniser is a finite-state machine which is rather verbose but by eliminating tests reduces the time for a large file from 85 secs to 25 secs. Each time a non-number character appears, the previous number is output. The initial string has a ficiticious B command added at the end as a terminator which saves the need to test for end of string and ensures final number is terminated.
The make_commands template removes any separators before the first command and passes the string to a recursive make_command template.
The only real complication is that commands do not have to have the command element in front of them. A succession of cubic beziers can just have an initial C followed by a multiple of 6 numbers following. Similarly for the other elements. In the case of the M element , subsequent pairs of numbers after the first pair represent lines.
make_command makes an estimate of the size of string needed to obtain the set of numbers that make up a single command and passes this to a template make_numbers. Some long sequences of parameters with no command letters do exist (several megabytes) and this avoids a recursion with large strings that will blow up the existing XSLT transformers.
Most of the transformation in the 3D system can be defined as 4 by 4 matrices. The aim here is to define the set of matrices in use. As with the 2D system we will assume that the matrices are applied right to left. The basic 2D transform is as follows:
The individual transformations are:
SVG 3D Axes
SVG 3D Axes
Perspective Transformation
xm = xh/wh and ym = yh/wh