The aim is to create one package that makes working with SVG paths nice and convenient.
Eventually, this package should replace (part of) the current separate implementations of SVG paths in existing packages.
I'll refer to the package here as OneTruePath, but we might want to pick a more serious name later.
advanced mathematics: Stuff like derivatives, tangents, interpolation is probably best implemented on top of OneTruePath. Maybe there are good practical reasons to implement mathematical funcions directly on svg instructions. We'll have to investigate what exactly makes sense to include here and what truely belongs in a separate package.
different backends:
This is an experiment to see what kind of API and data structure is convenient. Feedback is welcome.
The only important module for now is Path.
A path is a list of subpaths. A subpath is one moveto instruction (relative or absolute) followed by a list of drawto instructions (drawto includes all the other
instructions, like L, Q, etc.)
The main data strucures look like
type alias Path =
List SubPath
type alias SubPath =
{ moveto : MoveTo, drawtos : List DrawTo }
type MoveTo
= MoveTo Mode Coordinate
type DrawTo
= LineTo Mode (List Coordinate)
| Horizontal Mode (List Float)
| Vertical Mode (List Float)
| CurveTo Mode (List ( Coordinate, Coordinate, Coordinate ))
| SmoothCurveTo Mode (List ( Coordinate, Coordinate ))
| QuadraticBezierCurveTo Mode (List ( Coordinate, Coordinate ))
| SmoothQuadraticBezierCurveTo Mode (List Coordinate)
| EllipticArc Mode (List EllipticalArcArgument)
| ClosePath
type Mode = Absolute | Relative With the current api, we can write things like
myPath :: Path
myPath =
[ subpath (moveTo ( 0, 0 ))
[ lineTo [ ( 100, 0 ), ( 100, 100 ), ( 0, 100 ) ]
, closepath
]
, subpath (moveBy ( 42, 42 ))
[ arcTo
[ { radii = ( rx, ry )
, xAxisRotate = 0
, arcFlag = LargestArc
, direction = CounterClockwise
, target = ( x, y )
}
]
]This module also includes stringify functions to convert to something that the d attribute (and the parser) accepts.
I've included some code from the libraries, rewritten with the proposed api.
elm-style-animation
This module uses a custom type at the nodes of its PathCommand type. Ideally, the package would be changed to work more naturally with (sub)paths (i.e. enforcing a leading MoveTo). In any case it can use the low-level strinify functions.
arcToExample arc dx dy =
stringifyDrawTo <|
arcTo
[ { radii = ( arc.radius.position, arc.radius.position )
, xAxisRotate = 0
, arcFlag = LargestArc
, direction = CounterClockwise
, target = ( arc.x.position - dx, arc.y.position - dy )
}
, { radii = ( arc.radius.position, arc.radius.position )
, xAxisRotate = 0
, arcFlag = LargestArc
, direction = Clockwise
, target = ( arc.x.position + dx, arc.y.position + dy )
}
]elm-plot
Looks like it can use OneTruePath.Path and its api
linear : PlotSummary -> List Point -> Path
linear plot points =
let
-- conversion to svg coordinates
translation (x, y) = ( toSVGX plot x, toSVGY plot y)
in
case points of
[] -> []
p::ps ->
subpath (moveTo p) (lineTo points)
|> List.singleton
|> Path.mapCoordinates translation elm-visualization
Looks like it can use OneTruePath.Path and its api
subpath (moveTo (x, y))
[ horizontalBy [ w ]
, verticalBy [ h ]
, horizontalBy [ -w ]
, closepath
]opensolid/svg
Looks like it can use OneTruePath.Path and its api
openSolidExample x1 y1 x2 y2 x3 y3 =
subpath (Path.moveTo ( x1, y1 )) [ quadraticCurveTo [ ( x2, y2 ), ( x3, y3 ) ] ]
|> stringifySubPathsvg-path-dsl
will be deprecated when OneTruePath is released
Based on conversations at elm europe and searching package.elm-lang.org, these packages use svg paths:
Here are some observations and links to relevant types and functions
Only uses the absolute commands (relative ones are not expressable). Its Command type can be found here
The same file also defines a function that maps over all the coordinates in the structure (source) which can
be generalized to (Coordinate -> Coordinate) -> Path -> Path.
Only supports a subset of the path instructions, and adds some new ones (ArcCustom, Rect). The main data structure is PathSegment .
The custom ArcCustom instruction operates with a startAngle and endAngle, something that is also defined by elm-style-animation. This is not part of the SVG standard, but maybe useful enough in practice to include as a first-class instruction?
This package also keeps track of the cursor state when operating on the instructions. This is very convenient to have around - for concatenating paths but possibly much more.
This package contains separate instructions for commands with one argument L0,0 and with multiple arguments L1,1 2,3. With a fresh view, having one instruction is obviously better.
Otherwise, it is pretty similar to the Path type and its api proposed here.
elm-style-animation has a PathCommand type that is rendered by pathCmdValue. It looks like one constructor of the PathCommand type can construct multiple commands.
the opensolid/svg package uses polygon and polyline where possible, and draws arcs and curves using manual string concatenation. The parent opensolid/opensolid package defines curves and polylines in a more abstract way, and implements many mathematical operations on them.
Command:
Path makes a distinction (in the types) between MoveTo and DrawTo. In general that is great, but sometimes it is nicer to be able to easily compose
movetos and drawtos. Command is a wrapper for that, with conversion functions to and from Path.
We'll have to see how useful this is in practice. Path enforces correct svg (leading MoveTo) and in general I think it is nicer to think of svg as subpaths (moveto, list of drawtos) rather than a list of instructions without structure.
PathParser:
Parse some svg path syntax into an elm Path object.
ToAbsolute: convert any path to a path consisting solely of absolute instructions. Looking for ways to make this code nice
The main problem here is threading the CursorState record through the list of instructions. I've used my elm-state package to make this more convenient. (yup, it's the state monad, but this is a case where I genuinly think it makes code nicer, which is not often in elm)
Then there remains a function that describes what every instruction does to the DrawState, and that's just going to be a long function that will be tedious to write.
PlayGround: some random experiments/tests