package rough
import (
"math"
"regexp"
"strings"
)
type ellipseParams struct {
rx float64
ry float64
increment float64
}
type ellipseResult struct {
opset OpSet
estimatedPoints []Point
}
var rendererHelper = renderHelper {
randOffset : randOffset ,
randOffsetWithRange : randOffsetWithRange ,
ellipse : ellipse ,
doubleLineOps : doubleLineOps ,
}
func line(x1 , y1 , x2 , y2 float64 , o *ResolvedOptions ) OpSet {
return OpSet {Type : OpSetPath , Ops : doubleLine (x1 , y1 , x2 , y2 , o )}
}
func linearPath(points []Point , close bool , o *ResolvedOptions ) OpSet {
length := len (points )
if length > 2 {
ops := make ([]Op , 0 )
for i := 0 ; i < length -1 ; i ++ {
ops = append (ops , doubleLine (
points [i ][0 ], points [i ][1 ],
points [i +1 ][0 ], points [i +1 ][1 ],
o ,
)...)
}
if close {
ops = append (ops , doubleLine (
points [length -1 ][0 ], points [length -1 ][1 ],
points [0 ][0 ], points [0 ][1 ],
o ,
)...)
}
return OpSet {Type : OpSetPath , Ops : ops }
}
if length == 2 {
return line (points [0 ][0 ], points [0 ][1 ], points [1 ][0 ], points [1 ][1 ], o )
}
return OpSet {Type : OpSetPath , Ops : []Op {}}
}
func polygon(points []Point , o *ResolvedOptions ) OpSet {
return linearPath (points , true , o )
}
func rectangle(x , y , width , height float64 , o *ResolvedOptions ) OpSet {
return polygon ([]Point {
{x , y },
{x + width , y },
{x + width , y + height },
{x , y + height },
}, o )
}
func curve(points []Point , o *ResolvedOptions ) OpSet {
o1 := curveWithOffset (points , 1 *(1 +o .Roughness *0.2 ), o )
if o .DisableMultiStroke {
return OpSet {Type : OpSetPath , Ops : o1 }
}
o2Options := cloneOptionsAlterSeed (o )
o2 := curveWithOffset (points , 1.5 *(1 +o .Roughness *0.22 ), o2Options )
return OpSet {Type : OpSetPath , Ops : append (o1 , o2 ...)}
}
func ellipse(x , y , width , height float64 , o *ResolvedOptions ) OpSet {
params := generateEllipseParams (width , height , o )
return ellipseWithParams (x , y , o , params ).opset
}
func generateEllipseParams(width , height float64 , o *ResolvedOptions ) ellipseParams {
psq := math .Sqrt (math .Pi * 2 * math .Sqrt ((math .Pow (width /2 , 2 )+math .Pow (height /2 , 2 ))/2 ))
stepCount := math .Ceil (math .Max (o .CurveStepCount , (o .CurveStepCount /math .Sqrt (200 ))*psq ))
increment := (math .Pi * 2 ) / stepCount
rx := math .Abs (width / 2 )
ry := math .Abs (height / 2 )
curveFitRandomness := 1 - o .CurveFitting
rx += offsetOpt (rx *curveFitRandomness , o )
ry += offsetOpt (ry *curveFitRandomness , o )
return ellipseParams {increment : increment , rx : rx , ry : ry }
}
func ellipseWithParams(x , y float64 , o *ResolvedOptions , p ellipseParams ) ellipseResult {
ap1 , cp1 := computeEllipsePoints (
p .increment , x , y , p .rx , p .ry , 1 ,
p .increment *offset (0.1 , offset (0.4 , 1 , o ), o ),
o ,
)
o1 := roughCurve (ap1 , nil , o )
if !o .DisableMultiStroke && o .Roughness != 0 {
ap2 , _ := computeEllipsePoints (p .increment , x , y , p .rx , p .ry , 1.5 , 0 , o )
o1 = append (o1 , roughCurve (ap2 , nil , o )...)
}
return ellipseResult {
estimatedPoints : cp1 ,
opset : OpSet {
Type : OpSetPath ,
Ops : o1 ,
},
}
}
func arc(x , y , width , height , start , stop float64 , closed , roughClosure bool , o *ResolvedOptions ) OpSet {
cx , cy := x , y
rx := math .Abs (width / 2 )
ry := math .Abs (height / 2 )
rx += offsetOpt (rx *0.01 , o )
ry += offsetOpt (ry *0.01 , o )
strt , stp := start , stop
for strt < 0 {
strt += math .Pi * 2
stp += math .Pi * 2
}
if stp -strt > math .Pi *2 {
strt = 0
stp = math .Pi * 2
}
ellipseInc := (math .Pi * 2 ) / o .CurveStepCount
arcInc := math .Min (ellipseInc /2 , (stp -strt )/2 )
o1 := roughArc (arcInc , cx , cy , rx , ry , strt , stp , 1 , o )
ops := o1
if !o .DisableMultiStroke {
ops = append (ops , roughArc (arcInc , cx , cy , rx , ry , strt , stp , 1.5 , o )...)
}
if closed {
if roughClosure {
ops = append (ops , doubleLine (
cx , cy ,
cx +rx *math .Cos (strt ), cy +ry *math .Sin (strt ),
o ,
)...)
ops = append (ops , doubleLine (
cx , cy ,
cx +rx *math .Cos (stp ), cy +ry *math .Sin (stp ),
o ,
)...)
} else {
ops = append (ops ,
Op {Op : OpLineTo , Data : []float64 {cx , cy }},
Op {Op : OpLineTo , Data : []float64 {cx + rx *math .Cos (strt ), cy + ry *math .Sin (strt )}},
)
}
}
return OpSet {Type : OpSetPath , Ops : ops }
}
var (
pathNewlineRE = regexp .MustCompile (`\n` )
pathMinusSpaceRE = regexp .MustCompile (`-[\t\n\v\f\r \x{00a0}\x{1680}\x{2000}-\x{200a}\x{2028}\x{2029}\x{202f}\x{205f}\x{3000}\x{feff}]` )
pathLiteralSpaceBug = "/(ss)/g"
)
func svgPath(path string , o *ResolvedOptions ) OpSet {
p := newParsedPath (path )
if jsTruthyNumber (o .Simplification ) {
rp := newRoughPath (path )
fitter := newPathFitter (rp .linearPoints (), rp .closed ())
p = newParsedPath (fitter .fit (o .Simplification ))
}
ops := make ([]Op , 0 )
current , first := Point {}, Point {}
hasFirst := false
var previousCubicControl , previousQuadraticControl *Point
previousKey := ""
for _ , segment := range p .segments {
key := segment .key
relative := key >= "a" && key <= "z"
upper := strings .ToUpper (key )
data := append ([]float64 (nil ), segment .data ...)
absolutePoint := func (x , y float64 ) Point {
if relative {
return Point {x + current [0 ], y + current [1 ]}
}
return Point {x , y }
}
switch upper {
case "M" :
current = absolutePoint (data [0 ], data [1 ])
first , hasFirst = current , true
case "L" :
to := absolutePoint (data [0 ], data [1 ])
ops = append (ops , doubleLine (current [0 ], current [1 ], to [0 ], to [1 ], o )...)
current = to
case "H" :
x := data [0 ]
if relative {
x += current [0 ]
}
to := Point {x , current [1 ]}
ops = append (ops , doubleLine (current [0 ], current [1 ], to [0 ], to [1 ], o )...)
current = to
case "V" :
y := data [0 ]
if relative {
y += current [1 ]
}
to := Point {current [0 ], y }
ops = append (ops , doubleLine (current [0 ], current [1 ], to [0 ], to [1 ], o )...)
current = to
case "C" :
c1 := absolutePoint (data [0 ], data [1 ])
c2 := absolutePoint (data [2 ], data [3 ])
to := absolutePoint (data [4 ], data [5 ])
ops = append (ops , bezierToCurrent (c1 , c2 , to , current , o )...)
current = to
copy := c2
previousCubicControl = ©
case "S" :
c1 := current
if (previousKey == "C" || previousKey == "S" ) && previousCubicControl != nil {
c1 = Point {2 *current [0 ] - (*previousCubicControl )[0 ], 2 *current [1 ] - (*previousCubicControl )[1 ]}
}
c2 := absolutePoint (data [0 ], data [1 ])
to := absolutePoint (data [2 ], data [3 ])
ops = append (ops , bezierToCurrent (c1 , c2 , to , current , o )...)
current = to
copy := c2
previousCubicControl = ©
case "Q" :
q := absolutePoint (data [0 ], data [1 ])
to := absolutePoint (data [2 ], data [3 ])
c1 := Point {current [0 ] + (q [0 ]-current [0 ])*2 /3 , current [1 ] + (q [1 ]-current [1 ])*2 /3 }
c2 := Point {to [0 ] + (q [0 ]-to [0 ])*2 /3 , to [1 ] + (q [1 ]-to [1 ])*2 /3 }
ops = append (ops , bezierToCurrent (c1 , c2 , to , current , o )...)
current = to
copy := q
previousQuadraticControl = ©
case "T" :
q := current
if (previousKey == "Q" || previousKey == "T" ) && previousQuadraticControl != nil {
q = Point {2 *current [0 ] - (*previousQuadraticControl )[0 ], 2 *current [1 ] - (*previousQuadraticControl )[1 ]}
}
to := absolutePoint (data [0 ], data [1 ])
c1 := Point {current [0 ] + (q [0 ]-current [0 ])*2 /3 , current [1 ] + (q [1 ]-current [1 ])*2 /3 }
c2 := Point {to [0 ] + (q [0 ]-to [0 ])*2 /3 , to [1 ] + (q [1 ]-to [1 ])*2 /3 }
ops = append (ops , bezierToCurrent (c1 , c2 , to , current , o )...)
current = to
copy := q
previousQuadraticControl = ©
case "A" :
to := absolutePoint (data [5 ], data [6 ])
if to != current {
if data [0 ] == 0 || data [1 ] == 0 {
ops = append (ops , bezierToCurrent (current , to , to , current , o )...)
} else {
from := current
for _ , curve := range arcToCubicCurves (current [0 ], current [1 ], to [0 ], to [1 ], math .Abs (data [0 ]), math .Abs (data [1 ]), data [2 ], data [3 ], data [4 ], nil ) {
ops = append (ops , bezierToCurrent (Point {curve [0 ], curve [1 ]}, Point {curve [2 ], curve [3 ]}, Point {curve [4 ], curve [5 ]}, from , o )...)
from = Point {curve [4 ], curve [5 ]}
}
}
}
current = to
case "Z" :
if hasFirst {
ops = append (ops , doubleLine (current [0 ], current [1 ], first [0 ], first [1 ], o )...)
current = first
}
}
if upper != "C" && upper != "S" {
previousCubicControl = nil
}
if upper != "Q" && upper != "T" {
previousQuadraticControl = nil
}
previousKey = upper
}
return OpSet {Type : OpSetPath , Ops : ops }
}
func arcToCubicCurves(x1 , y1 , x2 , y2 , r1 , r2 , angle , largeArcFlag , sweepFlag float64 , recursive []float64 ) [][6 ]float64 {
angleRad := math .Pi * angle / 180
params := make ([]Point , 0 )
var f1 , f2 , cx , cy float64
if recursive != nil {
f1 , f2 , cx , cy = recursive [0 ], recursive [1 ], recursive [2 ], recursive [3 ]
} else {
x1 , y1 = rotatePathPoint (x1 , y1 , -angleRad )
x2 , y2 = rotatePathPoint (x2 , y2 , -angleRad )
x := (x1 - x2 ) / 2
y := (y1 - y2 ) / 2
h := x *x /(r1 *r1 ) + y *y /(r2 *r2 )
if h > 1 {
h = math .Sqrt (h )
r1 *= h
r2 *= h
}
sign := 1.0
if largeArcFlag == sweepFlag {
sign = -1
}
r1p , r2p := r1 *r1 , r2 *r2
left := r1p *r2p - r1p *y *y - r2p *x *x
right := r1p *y *y + r2p *x *x
k := sign * math .Sqrt (math .Abs (left /right ))
cx = k *r1 *y /r2 + (x1 +x2 )/2
cy = k *-r2 *x /r1 + (y1 +y2 )/2
f1 = math .Asin (jsToFixedNumber ((y1 -cy )/r2 , 9 ))
f2 = math .Asin (jsToFixedNumber ((y2 -cy )/r2 , 9 ))
if x1 < cx {
f1 = math .Pi - f1
}
if x2 < cx {
f2 = math .Pi - f2
}
if f1 < 0 {
f1 = math .Pi *2 + f1
}
if f2 < 0 {
f2 = math .Pi *2 + f2
}
if sweepFlag != 0 && f1 > f2 {
f1 -= math .Pi * 2
}
if sweepFlag == 0 && f2 > f1 {
f2 -= math .Pi * 2
}
}
df := f2 - f1
if math .Abs (df ) > math .Pi *120 /180 {
f2old , x2old , y2old := f2 , x2 , y2
if sweepFlag != 0 && f2 > f1 {
f2 = f1 + math .Pi *120 /180
} else {
f2 = f1 - math .Pi *120 /180
}
x2 = cx + r1 *math .Cos (f2 )
y2 = cy + r2 *math .Sin (f2 )
for _ , curve := range arcToCubicCurves (x2 , y2 , x2old , y2old , r1 , r2 , angle , 0 , sweepFlag , []float64 {f2 , f2old , cx , cy }) {
params = append (params , Point {curve [0 ], curve [1 ]}, Point {curve [2 ], curve [3 ]}, Point {curve [4 ], curve [5 ]})
}
}
df = f2 - f1
c1 , s1 , c2 , s2 := math .Cos (f1 ), math .Sin (f1 ), math .Cos (f2 ), math .Sin (f2 )
t := math .Tan (df / 4 )
hx , hy := 4.0 /3 *r1 *t , 4.0 /3 *r2 *t
m1 := Point {x1 , y1 }
m2 := Point {x1 + hx *s1 , y1 - hy *c1 }
m3 := Point {x2 + hx *s2 , y2 - hy *c2 }
m4 := Point {x2 , y2 }
m2 = Point {2 *m1 [0 ] - m2 [0 ], 2 *m1 [1 ] - m2 [1 ]}
params = append ([]Point {m2 , m3 , m4 }, params ...)
if recursive != nil {
curves := make ([][6 ]float64 , 0 , len (params )/3 )
for i := 0 ; i +2 < len (params ); i += 3 {
curves = append (curves , [6 ]float64 {params [i ][0 ], params [i ][1 ], params [i +1 ][0 ], params [i +1 ][1 ], params [i +2 ][0 ], params [i +2 ][1 ]})
}
return curves
}
curves := make ([][6 ]float64 , 0 , len (params )/3 )
for i := 0 ; i +2 < len (params ); i += 3 {
a , b := rotatePathPoint (params [i ][0 ], params [i ][1 ], angleRad )
c , d := rotatePathPoint (params [i +1 ][0 ], params [i +1 ][1 ], angleRad )
e , f := rotatePathPoint (params [i +2 ][0 ], params [i +2 ][1 ], angleRad )
curves = append (curves , [6 ]float64 {a , b , c , d , e , f })
}
return curves
}
func rotatePathPoint(x , y , angle float64 ) (float64 , float64 ) {
return x *math .Cos (angle ) - y *math .Sin (angle ), x *math .Sin (angle ) + y *math .Cos (angle )
}
func bezierToCurrent(c1 , c2 , to , current Point , o *ResolvedOptions ) []Op {
ros0 := jsOr (o .MaxRandomnessOffset , 1 )
ros := [2 ]float64 {ros0 , ros0 + 0.3 }
iterations := 2
if o .DisableMultiStroke {
iterations = 1
}
ops := make ([]Op , 0 , iterations *2 )
for i := 0 ; i < iterations ; i ++ {
move := current
if i != 0 && !o .PreserveVertices {
move = Point {current [0 ] + offsetOpt (ros [0 ], o ), current [1 ] + offsetOpt (ros [0 ], o )}
}
end := to
if !o .PreserveVertices {
end = Point {to [0 ] + offsetOpt (ros [i ], o ), to [1 ] + offsetOpt (ros [i ], o )}
}
ops = append (ops ,
Op {Op : OpMove , Data : []float64 {move [0 ], move [1 ]}},
Op {Op : OpBCurveTo , Data : []float64 {
c1 [0 ] + offsetOpt (ros [i ], o ), c1 [1 ] + offsetOpt (ros [i ], o ),
c2 [0 ] + offsetOpt (ros [i ], o ), c2 [1 ] + offsetOpt (ros [i ], o ),
end [0 ], end [1 ],
}},
)
}
return ops
}
func solidFillPolygon(points []Point , o *ResolvedOptions ) OpSet {
return solidFillPolygons ([][]Point {points }, o )
}
func solidFillPolygons(polygonList [][]Point , o *ResolvedOptions ) OpSet {
ops := make ([]Op , 0 )
for _ , points := range polygonList {
randOffset := jsOr (o .MaxRandomnessOffset , 0 )
if len (points ) > 2 {
ops = append (ops , Op {Op : OpMove , Data : []float64 {
points [0 ][0 ] + offsetOpt (randOffset , o ),
points [0 ][1 ] + offsetOpt (randOffset , o ),
}})
for i := 1 ; i < len (points ); i ++ {
ops = append (ops , Op {Op : OpLineTo , Data : []float64 {
points [i ][0 ] + offsetOpt (randOffset , o ),
points [i ][1 ] + offsetOpt (randOffset , o ),
}})
}
}
}
return OpSet {Type : OpSetFillPath , Ops : ops }
}
func patternFillPolygon(points []Point , o *ResolvedOptions ) OpSet {
return getFiller (o , rendererHelper ).fillPolygon (points , o )
}
func patternFillArc(x , y , width , height , start , stop float64 , o *ResolvedOptions ) OpSet {
cx , cy := x , y
rx := math .Abs (width / 2 )
ry := math .Abs (height / 2 )
rx += offsetOpt (rx *0.01 , o )
ry += offsetOpt (ry *0.01 , o )
strt , stp := start , stop
for strt < 0 {
strt += math .Pi * 2
stp += math .Pi * 2
}
if stp -strt > math .Pi *2 {
strt = 0
stp = math .Pi * 2
}
increment := (stp - strt ) / o .CurveStepCount
points := make ([]Point , 0 )
for angle := strt ; angle <= stp ; angle += increment {
points = append (points , Point {cx + rx *math .Cos (angle ), cy + ry *math .Sin (angle )})
}
points = append (points ,
Point {cx + rx *math .Cos (stp ), cy + ry *math .Sin (stp )},
Point {cx , cy },
)
return patternFillPolygon (points , o )
}
func randOffset(x float64 , o *ResolvedOptions ) float64 {
return offsetOpt (x , o )
}
func randOffsetWithRange(minimum , maximum float64 , o *ResolvedOptions ) float64 {
return offset (minimum , maximum , o )
}
func doubleLineOps(x1 , y1 , x2 , y2 float64 , o *ResolvedOptions ) []Op {
return doubleLineFill (x1 , y1 , x2 , y2 , o )
}
func offset(minimum , maximum float64 , o *ResolvedOptions , roughnessGain ...float64 ) float64 {
gain := 1.0
if len (roughnessGain ) != 0 {
gain = roughnessGain [0 ]
}
return o .Roughness * gain * (random (o )*(maximum -minimum ) + minimum )
}
func offsetOpt(x float64 , o *ResolvedOptions , roughnessGain ...float64 ) float64 {
return offset (-x , x , o , roughnessGain ...)
}
func doubleLine(x1 , y1 , x2 , y2 float64 , o *ResolvedOptions ) []Op {
return doubleLineMode (x1 , y1 , x2 , y2 , o , false )
}
func doubleLineFill(x1 , y1 , x2 , y2 float64 , o *ResolvedOptions ) []Op {
return doubleLineMode (x1 , y1 , x2 , y2 , o , true )
}
func doubleLineMode(x1 , y1 , x2 , y2 float64 , o *ResolvedOptions , filling bool ) []Op {
o1 := roughLine (x1 , y1 , x2 , y2 , o , true , false )
if (!filling && o .DisableMultiStroke ) || (filling && o .DisableMultiStrokeFill ) {
return o1
}
o2 := roughLine (x1 , y1 , x2 , y2 , o , true , true )
return append (o1 , o2 ...)
}
func roughLine(x1 , y1 , x2 , y2 float64 , o *ResolvedOptions , move , overlay bool ) []Op {
lengthSq := math .Pow (x1 -x2 , 2 ) + math .Pow (y1 -y2 , 2 )
length := math .Sqrt (lengthSq )
roughnessGain := 1.0
if length < 200 {
roughnessGain = 1
} else if length > 500 {
roughnessGain = 0.4
} else {
scaledLength := jsNumberStep (-0.0016668 * length )
roughnessGain = scaledLength + 1.233334
}
randOffset := jsOr (o .MaxRandomnessOffset , 0 )
if randOffset *randOffset *100 > lengthSq {
randOffset = length / 10
}
halfOffset := randOffset / 2
divergePoint := 0.2 + random (o )*0.2
midDispX := o .Bowing * o .MaxRandomnessOffset * (y2 - y1 ) / 200
midDispY := o .Bowing * o .MaxRandomnessOffset * (x1 - x2 ) / 200
midDispX = offsetOpt (midDispX , o , roughnessGain )
midDispY = offsetOpt (midDispY , o , roughnessGain )
randomHalf := func () float64 { return offsetOpt (halfOffset , o , roughnessGain ) }
randomFull := func () float64 { return offsetOpt (randOffset , o , roughnessGain ) }
preserveVertices := o .PreserveVertices
ops := make ([]Op , 0 , 2 )
if move {
if overlay {
ops = append (ops , Op {Op : OpMove , Data : []float64 {
x1 + jsConditionalFloat (preserveVertices , 0 , randomHalf ),
y1 + jsConditionalFloat (preserveVertices , 0 , randomHalf ),
}})
} else {
ops = append (ops , Op {Op : OpMove , Data : []float64 {
x1 + jsConditionalFloat (preserveVertices , 0 , randomFull ),
y1 + jsConditionalFloat (preserveVertices , 0 , randomFull ),
}})
}
}
if overlay {
ops = append (ops , Op {Op : OpBCurveTo , Data : []float64 {
midDispX + x1 + (x2 -x1 )*divergePoint + randomHalf (),
midDispY + y1 + (y2 -y1 )*divergePoint + randomHalf (),
midDispX + x1 + 2 *(x2 -x1 )*divergePoint + randomHalf (),
midDispY + y1 + 2 *(y2 -y1 )*divergePoint + randomHalf (),
x2 + jsConditionalFloat (preserveVertices , 0 , randomHalf ),
y2 + jsConditionalFloat (preserveVertices , 0 , randomHalf ),
}})
} else {
ops = append (ops , Op {Op : OpBCurveTo , Data : []float64 {
midDispX + x1 + (x2 -x1 )*divergePoint + randomFull (),
midDispY + y1 + (y2 -y1 )*divergePoint + randomFull (),
midDispX + x1 + 2 *(x2 -x1 )*divergePoint + randomFull (),
midDispY + y1 + 2 *(y2 -y1 )*divergePoint + randomFull (),
x2 + jsConditionalFloat (preserveVertices , 0 , randomFull ),
y2 + jsConditionalFloat (preserveVertices , 0 , randomFull ),
}})
}
return ops
}
func curveWithOffset(points []Point , randOffset float64 , o *ResolvedOptions ) []Op {
if len (points ) == 0 {
return []Op {}
}
ps := make ([]Point , 0 , len (points )+2 )
ps = append (ps ,
Point {points [0 ][0 ] + offsetOpt (randOffset , o ), points [0 ][1 ] + offsetOpt (randOffset , o )},
Point {points [0 ][0 ] + offsetOpt (randOffset , o ), points [0 ][1 ] + offsetOpt (randOffset , o )},
)
for i := 1 ; i < len (points ); i ++ {
ps = append (ps , Point {
points [i ][0 ] + offsetOpt (randOffset , o ),
points [i ][1 ] + offsetOpt (randOffset , o ),
})
if i == len (points )-1 {
ps = append (ps , Point {
points [i ][0 ] + offsetOpt (randOffset , o ),
points [i ][1 ] + offsetOpt (randOffset , o ),
})
}
}
return roughCurve (ps , nil , o )
}
func roughCurve(points []Point , closePoint *Point , o *ResolvedOptions ) []Op {
length := len (points )
ops := make ([]Op , 0 )
if length > 3 {
s := 1 - o .CurveTightness
ops = append (ops , Op {Op : OpMove , Data : []float64 {points [1 ][0 ], points [1 ][1 ]}})
for i := 1 ; i +2 < length ; i ++ {
cached := points [i ]
b1 := Point {
cached [0 ] + (s *points [i +1 ][0 ]-s *points [i -1 ][0 ])/6 ,
cached [1 ] + (s *points [i +1 ][1 ]-s *points [i -1 ][1 ])/6 ,
}
b2 := Point {
points [i +1 ][0 ] + (s *points [i ][0 ]-s *points [i +2 ][0 ])/6 ,
points [i +1 ][1 ] + (s *points [i ][1 ]-s *points [i +2 ][1 ])/6 ,
}
b3 := points [i +1 ]
ops = append (ops , Op {Op : OpBCurveTo , Data : []float64 {
b1 [0 ], b1 [1 ], b2 [0 ], b2 [1 ], b3 [0 ], b3 [1 ],
}})
}
if closePoint != nil {
ro := o .MaxRandomnessOffset
ops = append (ops , Op {Op : OpLineTo , Data : []float64 {
(*closePoint )[0 ] + offsetOpt (ro , o ),
(*closePoint )[1 ] + offsetOpt (ro , o ),
}})
}
} else if length == 3 {
ops = append (ops ,
Op {Op : OpMove , Data : []float64 {points [1 ][0 ], points [1 ][1 ]}},
Op {Op : OpBCurveTo , Data : []float64 {
points [1 ][0 ], points [1 ][1 ],
points [2 ][0 ], points [2 ][1 ],
points [2 ][0 ], points [2 ][1 ],
}},
)
} else if length == 2 {
ops = append (ops , doubleLine (
points [0 ][0 ], points [0 ][1 ],
points [1 ][0 ], points [1 ][1 ],
o ,
)...)
}
return ops
}
func computeEllipsePoints(increment , cx , cy , rx , ry , randOffset , overlap float64 , o *ResolvedOptions ) ([]Point , []Point ) {
corePoints := make ([]Point , 0 )
allPoints := make ([]Point , 0 )
if o .Roughness == 0 {
increment /= 4
allPoints = append (allPoints , Point {cx + rx *math .Cos (-increment ), cy + ry *math .Sin (-increment )})
for angle := 0.0 ; angle <= math .Pi *2 ; angle += increment {
p := Point {cx + rx *math .Cos (angle ), cy + ry *math .Sin (angle )}
corePoints = append (corePoints , p )
allPoints = append (allPoints , p )
}
allPoints = append (allPoints ,
Point {cx + rx , cy },
Point {cx + rx *math .Cos (increment ), cy + ry *math .Sin (increment )},
)
return allPoints , corePoints
}
radOffset := offsetOpt (0.5 , o ) - math .Pi /2
allPoints = append (allPoints , Point {
offsetOpt (randOffset , o ) + cx + 0.9 *rx *math .Cos (radOffset -increment ),
offsetOpt (randOffset , o ) + cy + 0.9 *ry *math .Sin (radOffset -increment ),
})
for angle := radOffset ; angle < math .Pi *2 +radOffset -0.01 ; angle += increment {
p := Point {
offsetOpt (randOffset , o ) + cx + rx *math .Cos (angle ),
offsetOpt (randOffset , o ) + cy + ry *math .Sin (angle ),
}
corePoints = append (corePoints , p )
allPoints = append (allPoints , p )
}
allPoints = append (allPoints ,
Point {
offsetOpt (randOffset , o ) + cx + rx *math .Cos (radOffset +math .Pi *2 +overlap *0.5 ),
offsetOpt (randOffset , o ) + cy + ry *math .Sin (radOffset +math .Pi *2 +overlap *0.5 ),
},
Point {
offsetOpt (randOffset , o ) + cx + 0.98 *rx *math .Cos (radOffset +overlap ),
offsetOpt (randOffset , o ) + cy + 0.98 *ry *math .Sin (radOffset +overlap ),
},
Point {
offsetOpt (randOffset , o ) + cx + 0.9 *rx *math .Cos (radOffset +overlap *0.5 ),
offsetOpt (randOffset , o ) + cy + 0.9 *ry *math .Sin (radOffset +overlap *0.5 ),
},
)
return allPoints , corePoints
}
func roughArc(increment , cx , cy , rx , ry , strt , stp , randOffset float64 , o *ResolvedOptions ) []Op {
radOffset := strt + offsetOpt (0.1 , o )
points := make ([]Point , 0 )
points = append (points , Point {
offsetOpt (randOffset , o ) + cx + 0.9 *rx *math .Cos (radOffset -increment ),
offsetOpt (randOffset , o ) + cy + 0.9 *ry *math .Sin (radOffset -increment ),
})
for angle := radOffset ; angle <= stp ; angle += increment {
points = append (points , Point {
offsetOpt (randOffset , o ) + cx + rx *math .Cos (angle ),
offsetOpt (randOffset , o ) + cy + ry *math .Sin (angle ),
})
}
end := Point {cx + rx *math .Cos (stp ), cy + ry *math .Sin (stp )}
points = append (points , end , end )
return roughCurve (points , nil , o )
}
func bezierTo(x1 , y1 , x2 , y2 , x , y float64 , path *roughPath , o *ResolvedOptions ) []Op {
ros0 := jsOr (o .MaxRandomnessOffset , 1 )
ros := [2 ]float64 {ros0 , ros0 + 0.3 }
ops := make ([]Op , 0 , 4 )
f := Point {0 , 0 }
iterations := 2
if o .DisableMultiStroke {
iterations = 1
}
for i := 0 ; i < iterations ; i ++ {
if i == 0 {
ops = append (ops , Op {Op : OpMove , Data : []float64 {path .x (), path .y ()}})
} else {
ops = append (ops , Op {Op : OpMove , Data : []float64 {
path .x () + jsConditionalFloat (o .PreserveVertices , 0 , func () float64 { return offsetOpt (ros [0 ], o ) }),
path .y () + jsConditionalFloat (o .PreserveVertices , 0 , func () float64 { return offsetOpt (ros [0 ], o ) }),
}})
}
if o .PreserveVertices {
f = Point {x , y }
} else {
f = Point {x + offsetOpt (ros [i ], o ), y + offsetOpt (ros [i ], o )}
}
ops = append (ops , Op {Op : OpBCurveTo , Data : []float64 {
x1 + offsetOpt (ros [i ], o ), y1 + offsetOpt (ros [i ], o ),
x2 + offsetOpt (ros [i ], o ), y2 + offsetOpt (ros [i ], o ),
f [0 ], f [1 ],
}})
}
path .setPosition (f [0 ], f [1 ])
return ops
}
func cloneOptionsAlterSeed(options *ResolvedOptions ) *ResolvedOptions {
copy := cloneResolvedOptions (*options )
copy .randomizer = nil
if options .Seed != 0 {
copy .Seed = options .Seed + 1
}
return ©
}
func jsConditionalFloat(condition bool , whenTrue float64 , whenFalse func () float64 ) float64 {
if condition {
return whenTrue
}
return whenFalse ()
}
func processSegment(path *roughPath , seg , prevSeg *pathSegment , o *ResolvedOptions ) []Op {
ops := make ([]Op , 0 )
switch seg .key {
case "M" , "m" :
delta := seg .key == "m"
if len (seg .data ) >= 2 {
x , y := seg .data [0 ], seg .data [1 ]
if delta {
x += path .x ()
y += path .y ()
}
ro := jsOr (o .MaxRandomnessOffset , 0 )
x += offsetOpt (ro , o )
y += offsetOpt (ro , o )
path .setPosition (x , y )
ops = append (ops , Op {Op : OpMove , Data : []float64 {x , y }})
}
case "L" , "l" :
delta := seg .key == "l"
if len (seg .data ) >= 2 {
x , y := seg .data [0 ], seg .data [1 ]
if delta {
x += path .x ()
y += path .y ()
}
ops = append (ops , doubleLine (path .x (), path .y (), x , y , o )...)
path .setPosition (x , y )
}
case "H" , "h" :
delta := seg .key == "h"
if len (seg .data ) > 0 {
x := seg .data [0 ]
if delta {
x += path .x ()
}
ops = append (ops , doubleLine (path .x (), path .y (), x , path .y (), o )...)
path .setPosition (x , path .y ())
}
case "V" , "v" :
delta := seg .key == "v"
if len (seg .data ) > 0 {
y := seg .data [0 ]
if delta {
y += path .y ()
}
ops = append (ops , doubleLine (path .x (), path .y (), path .x (), y , o )...)
path .setPosition (path .x (), y )
}
case "Z" , "z" :
if path .first != nil {
ops = append (ops , doubleLine (path .x (), path .y (), (*path .first )[0 ], (*path .first )[1 ], o )...)
path .setPosition ((*path .first )[0 ], (*path .first )[1 ])
path .first = nil
}
case "C" , "c" :
delta := seg .key == "c"
if len (seg .data ) >= 6 {
x1 , y1 := seg .data [0 ], seg .data [1 ]
x2 , y2 := seg .data [2 ], seg .data [3 ]
x , y := seg .data [4 ], seg .data [5 ]
if delta {
x1 += path .x ()
x2 += path .x ()
x += path .x ()
y1 += path .y ()
y2 += path .y ()
y += path .y ()
}
ops = append (ops , bezierTo (x1 , y1 , x2 , y2 , x , y , path , o )...)
ref := Point {x + (x - x2 ), y + (y - y2 )}
path .bezierReflectionPoint = &ref
}
case "S" , "s" :
delta := seg .key == "s"
if len (seg .data ) >= 4 {
x2 , y2 := seg .data [0 ], seg .data [1 ]
x , y := seg .data [2 ], seg .data [3 ]
if delta {
x2 += path .x ()
x += path .x ()
y2 += path .y ()
y += path .y ()
}
x1 , y1 := x2 , y2
prevKey := ""
if prevSeg != nil {
prevKey = prevSeg .key
}
if prevKey == "c" || prevKey == "C" || prevKey == "s" || prevKey == "S" {
if path .bezierReflectionPoint != nil {
x1 , y1 = (*path .bezierReflectionPoint )[0 ], (*path .bezierReflectionPoint )[1 ]
}
}
ops = append (ops , bezierTo (x1 , y1 , x2 , y2 , x , y , path , o )...)
ref := Point {x + (x - x2 ), y + (y - y2 )}
path .bezierReflectionPoint = &ref
}
case "Q" , "q" :
delta := seg .key == "q"
if len (seg .data ) >= 4 {
x1 , y1 := seg .data [0 ], seg .data [1 ]
x , y := seg .data [2 ], seg .data [3 ]
if delta {
x1 += path .x ()
x += path .x ()
y1 += path .y ()
y += path .y ()
}
offset1 := 1 * (1 + o .Roughness *0.2 )
offset2 := 1.5 * (1 + o .Roughness *0.22 )
ops = append (ops , Op {Op : OpMove , Data : []float64 {
path .x () + offsetOpt (offset1 , o ), path .y () + offsetOpt (offset1 , o ),
}})
f := Point {x + offsetOpt (offset1 , o ), y + offsetOpt (offset1 , o )}
ops = append (ops , Op {Op : OpQCurveTo , Data : []float64 {
x1 + offsetOpt (offset1 , o ), y1 + offsetOpt (offset1 , o ), f [0 ], f [1 ],
}})
ops = append (ops , Op {Op : OpMove , Data : []float64 {
path .x () + offsetOpt (offset2 , o ), path .y () + offsetOpt (offset2 , o ),
}})
f = Point {x + offsetOpt (offset2 , o ), y + offsetOpt (offset2 , o )}
ops = append (ops , Op {Op : OpQCurveTo , Data : []float64 {
x1 + offsetOpt (offset2 , o ), y1 + offsetOpt (offset2 , o ), f [0 ], f [1 ],
}})
path .setPosition (f [0 ], f [1 ])
ref := Point {x + (x - x1 ), y + (y - y1 )}
path .quadReflectionPoint = &ref
}
case "T" , "t" :
delta := seg .key == "t"
if len (seg .data ) >= 2 {
x , y := seg .data [0 ], seg .data [1 ]
if delta {
x += path .x ()
y += path .y ()
}
x1 , y1 := x , y
prevKey := ""
if prevSeg != nil {
prevKey = prevSeg .key
}
if prevKey == "q" || prevKey == "Q" || prevKey == "t" || prevKey == "T" {
if path .quadReflectionPoint != nil {
x1 , y1 = (*path .quadReflectionPoint )[0 ], (*path .quadReflectionPoint )[1 ]
}
}
offset1 := 1 * (1 + o .Roughness *0.2 )
offset2 := 1.5 * (1 + o .Roughness *0.22 )
ops = append (ops , Op {Op : OpMove , Data : []float64 {
path .x () + offsetOpt (offset1 , o ), path .y () + offsetOpt (offset1 , o ),
}})
f := Point {x + offsetOpt (offset1 , o ), y + offsetOpt (offset1 , o )}
ops = append (ops , Op {Op : OpQCurveTo , Data : []float64 {
x1 + offsetOpt (offset1 , o ), y1 + offsetOpt (offset1 , o ), f [0 ], f [1 ],
}})
ops = append (ops , Op {Op : OpMove , Data : []float64 {
path .x () + offsetOpt (offset2 , o ), path .y () + offsetOpt (offset2 , o ),
}})
f = Point {x + offsetOpt (offset2 , o ), y + offsetOpt (offset2 , o )}
ops = append (ops , Op {Op : OpQCurveTo , Data : []float64 {
x1 + offsetOpt (offset2 , o ), y1 + offsetOpt (offset2 , o ), f [0 ], f [1 ],
}})
path .setPosition (f [0 ], f [1 ])
ref := Point {x + (x - x1 ), y + (y - y1 )}
path .quadReflectionPoint = &ref
}
case "A" , "a" :
delta := seg .key == "a"
if len (seg .data ) >= 7 {
rx , ry := seg .data [0 ], seg .data [1 ]
angle := seg .data [2 ]
largeArcFlag := seg .data [3 ]
sweepFlag := seg .data [4 ]
x , y := seg .data [5 ], seg .data [6 ]
if delta {
x += path .x ()
y += path .y ()
}
if x == path .x () && y == path .y () {
break
}
if rx == 0 || ry == 0 {
ops = append (ops , doubleLine (path .x (), path .y (), x , y , o )...)
path .setPosition (x , y )
} else {
arcConverter := newRoughArcConverter (
Point {path .x (), path .y ()}, Point {x , y }, Point {rx , ry }, angle ,
jsTruthyNumber (largeArcFlag ), jsTruthyNumber (sweepFlag ),
)
for segment := arcConverter .getNextSegment (); segment != nil ; segment = arcConverter .getNextSegment () {
ops = append (ops , bezierTo (
segment .cp1 [0 ], segment .cp1 [1 ],
segment .cp2 [0 ], segment .cp2 [1 ],
segment .to [0 ], segment .to [1 ],
path , o ,
)...)
}
}
}
}
return ops
}
func jsTruthyNumber(v float64 ) bool {
return v != 0 && !math .IsNaN (v )
}
func jsOr(v , fallback float64 ) float64 {
if jsTruthyNumber (v ) {
return v
}
return fallback
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .