Skip to content

Commit

Permalink
implement smooth curve shorthands
Browse files Browse the repository at this point in the history
  • Loading branch information
folkertdev committed Nov 7, 2017
1 parent 9bb05e7 commit 1687e90
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 57 deletions.
57 changes: 28 additions & 29 deletions src/Curve.elm
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,12 @@ A nice consequence is that there are no weird bumps in the curve between the dat
-}

import List.Extra as List
import LowLevel.Command as LowLevel exposing (..)
import LowLevel.Command as Command exposing (..)
import SubPath exposing (SubPath(..), close, connect, empty, subpath)
import Vector2 as Vec2 exposing (Vec2)
import Vector3 exposing (Vec3)
import Internal.NaturalInterpolation exposing (naturalControlPoints)
import Path.LowLevel as LowLevel exposing (Mode(Absolute))


epsilon : Float
Expand Down Expand Up @@ -154,7 +155,7 @@ linear points =

{-| Shorthand to draw a sequence of cubic bezier segments
-}
cubicBezier : Vec2 Float -> List (Vec3 (Vec2 Float)) -> SubPath
cubicBezier : Vec2 Float -> List ( Vec2 Float, Vec2 Float, Vec2 Float ) -> SubPath
cubicBezier start points =
case points of
[] ->
Expand All @@ -166,25 +167,24 @@ cubicBezier start points =

{-| Shorthand to draw a sequence of smooth cubic bezier segments
-}
smoothCubicBezier : Vec2 Float -> Vec3 (Vec2 Float) -> List (Vec2 (Vec2 Float)) -> SubPath
smoothCubicBezier : Vec2 Float -> ( Vec2 Float, Vec2 Float, Vec2 Float ) -> List ( Vec2 Float, Vec2 Float ) -> SubPath
smoothCubicBezier start first points =
Debug.crash "todo "



{-
case points of
[] ->
empty
x :: xs ->
subpath (moveTo start) [ cubicCurveTo [ first ], cubicCurveExtendTo points ]
-}
let
lowLevelDrawTos =
[ LowLevel.CurveTo Absolute [ first ]
, LowLevel.SmoothCurveTo Absolute points
]

lowLevelSubPath : LowLevel.SubPath
lowLevelSubPath =
{ moveto = LowLevel.MoveTo Absolute start, drawtos = lowLevelDrawTos }
in
SubPath.fromLowLevel lowLevelSubPath


{-| Shorthand to draw a sequence of quadratic bezier segments
-}
quadraticBezier : Vec2 Float -> List (Vec2 (Vec2 Float)) -> SubPath
quadraticBezier : Vec2 Float -> List ( Vec2 Float, Vec2 Float ) -> SubPath
quadraticBezier start points =
case points of
[] ->
Expand All @@ -196,20 +196,19 @@ quadraticBezier start points =

{-| Shorthand to draw a sequence of smooth quadratic bezier segments
-}
smoothQuadraticBezier : Vec2 Float -> Vec2 (Vec2 Float) -> List (Vec2 Float) -> SubPath
smoothQuadraticBezier : Vec2 Float -> ( Vec2 Float, Vec2 Float ) -> List (Vec2 Float) -> SubPath
smoothQuadraticBezier start first points =
Debug.crash "todo"



{-
case points of
[] ->
empty
x :: xs ->
subpath (moveTo start) [ quadraticCurveTo [ first ], quadraticCurveExtendTo points ]
-}
let
lowLevelDrawTos =
[ LowLevel.QuadraticBezierCurveTo Absolute [ first ]
, LowLevel.SmoothQuadraticBezierCurveTo Absolute points
]

lowLevelSubPath : LowLevel.SubPath
lowLevelSubPath =
{ moveto = LowLevel.MoveTo Absolute start, drawtos = lowLevelDrawTos }
in
SubPath.fromLowLevel lowLevelSubPath


{-| Draw a straigt line between the data points, connecting the ends.
Expand Down
48 changes: 47 additions & 1 deletion src/Path.elm
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module Path
, element
, parse
, toString
, fromLowLevel
, toLowLevel
)

{-| Module for layering SubPaths into Paths.
Expand All @@ -27,11 +29,17 @@ Most of the interesting stuff happens in the `SubPath` and `Curve` modules.
@docs element, toString
## Conversion
@docs fromLowLevel, toLowLevel
-}

import Parser
import Path.LowLevel.Parser as PathParser
import Path.LowLevel as LowLevel
import SubPath exposing (SubPath, subpath)
import LowLevel.Command as Command
import Svg
import Svg.Attributes

Expand Down Expand Up @@ -96,4 +104,42 @@ The error type is [`Parser.Error`](http://package.elm-lang.org/packages/elm-tool
-}
parse : String -> Result Parser.Error Path
parse =
Result.map SubPath.fromLowLevel << PathParser.parse
Result.map fromLowLevel << PathParser.parse


{-| Converting a svg-path-lowlevel subpath into a one-true-path subpath. Used in parsing
-}
fromLowLevel : List LowLevel.SubPath -> Path
fromLowLevel lowlevels =
case lowlevels of
[] ->
[]

first :: _ ->
-- first moveto is always interpreted absolute
case first.moveto of
LowLevel.MoveTo _ target ->
let
initialCursorState =
{ start = target, cursor = target, previousControlPoint = Nothing }

folder { moveto, drawtos } ( state, accum ) =
let
( stateAfterMoveTo, newMoveTo ) =
Command.fromLowLevelMoveTo moveto state

( stateAfterDrawtos, newDrawTos ) =
Command.fromLowLevelDrawTos drawtos stateAfterMoveTo
in
( stateAfterDrawtos, subpath newMoveTo newDrawTos :: accum )
in
List.foldl folder ( initialCursorState, [] ) lowlevels
|> Tuple.second
|> List.reverse


{-| Convert a path to a svg-path-lowlevel list of subpaths
-}
toLowLevel : Path -> List LowLevel.SubPath
toLowLevel =
List.filterMap SubPath.toLowLevel
39 changes: 12 additions & 27 deletions src/SubPath.elm
Original file line number Diff line number Diff line change
Expand Up @@ -518,34 +518,19 @@ scale vec subpath =


{-| Converting a svg-path-lowlevel subpath into a one-true-path subpath. Used in parsing
-}
fromLowLevel : List LowLevel.SubPath -> List SubPath
fromLowLevel lowlevels =
case lowlevels of
[] ->
[]

first :: _ ->
-- first moveto is always interpreted absolute
case first.moveto of
LowLevel.MoveTo _ target ->
let
initialCursorState =
{ start = target, cursor = target, previousControlPoint = Nothing }

folder { moveto, drawtos } ( state, accum ) =
let
( stateAfterMoveTo, newMoveTo ) =
Command.fromLowLevelMoveTo moveto state
( stateAfterDrawtos, newDrawTos ) =
Command.fromLowLevelDrawTos drawtos stateAfterMoveTo
in
( stateAfterDrawtos, SubPath { moveto = newMoveTo, drawtos = Deque.fromList newDrawTos } :: accum )
in
List.foldl folder ( initialCursorState, [] ) lowlevels
|> Tuple.second
|> List.reverse
> Beware that the moveto is always interpreted as **Absolute**.
-}
fromLowLevel : LowLevel.SubPath -> SubPath
fromLowLevel { moveto, drawtos } =
-- first moveto is always interpreted absolute
case moveto of
LowLevel.MoveTo _ target ->
let
initialCursorState =
{ start = target, cursor = target, previousControlPoint = Nothing }
in
subpath (MoveTo target) (Tuple.second <| Command.fromLowLevelDrawTos drawtos initialCursorState)


{-| Converting a one-true-path subpath into a svg-path-lowlevel subpath. Used in toString
Expand Down
14 changes: 14 additions & 0 deletions tests/PathTest.elm
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,20 @@ various =
( { startConfig | cursor = newCursor, start = newStart, previousControlPoint = Nothing }
, MoveTo newCursor
)
, test "parsing and conversion of smooth quadratic is correct" <|
\_ ->
"M10 80 Q 52.5 10, 95 80 T 180 80"
|> Path.parse
|> Result.withDefault []
|> Path.toString
|> Expect.equal "M10,80 Q52.5,10 95,80 Q137.5,150 180,80"
, test "parsing and conversion of smooth cubic is correct" <|
\_ ->
"M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80"
|> Path.parse
|> Result.withDefault []
|> Path.toString
|> Expect.equal "M10,80 C40,10 65,10 95,80 C125,150 150,150 180,80"
]


Expand Down

0 comments on commit 1687e90

Please sign in to comment.