Iosevka/support/spiroexpand.patel

167 lines
No EOL
5.8 KiB
Text

define smooth [require './monotonic-interpolate'].createInterpolant
define tp [require './transform'].transformPoint
define utp [require './transform'].untransform
define [fallback] : for [local j 0] [j < arguments.length] [inc j] : if [arguments`j !== nothing] : return arguments`j
define [linreg x0 y0 x1 y1 x] : y0 + [x - x0] * [y1 - y0] / [x1 - x0]
define [SpiroExpansionContext] : begin {
set this.gizmo (.xx 1 .yy 1 .xy 0 .yy 0 .x 0 .y 0)
set this.controlKnots ()
set this.defaultd1 0
set this.defaultd2 0
return nothing
}
define [SpiroExpansionContext.prototype.moveTo x y unimportant] : begin {
if unimportant : return nothing
# Transform incoming knots using gizmo
set (.x x .y y) : tp this.gizmo (.x x .y y)
this.controlKnots.push (.x x .y y .type 'g4' .d1 this.defaultd1 .d2 this.defaultd2)
}
define [SpiroExpansionContext.prototype.lineTo x y unimportant] : begin {
local lastKnot this.controlKnots.[this.controlKnots.length - 1]
set (.x x .y y) : tp this.gizmo (.x x .y y)
local thisKnot (.x x .y y .type 'g4' .d1 lastKnot.d1 .d2 lastKnot.d2)
if lastKnot : begin {
local normalAngle : Math.PI / 2 + [Math.atan2 [y - lastKnot.y] [x - lastKnot.x]]
set thisKnot.normalAngle normalAngle
if [lastKnot.normalAngle === nothing] : set lastKnot.normalAngle normalAngle
}
if [not unimportant] : this.controlKnots.push thisKnot
}
define [SpiroExpansionContext.prototype.cubicTo x1 y1 x2 y2 x y unimportant] : begin {
local lastKnot this.controlKnots.[this.controlKnots.length - 1]
set (.x x1 .y y1) : tp this.gizmo (.x x1 .y y1)
set (.x x2 .y y2) : tp this.gizmo (.x x2 .y y2)
set (.x x .y y) : tp this.gizmo (.x x .y y)
local thisKnot (.x x .y y .type 'g4' .d1 lastKnot.d1 .d2 lastKnot.d2)
if [lastKnot && lastKnot.normalAngle === nothing] : begin {
local normalAngle : Math.PI / 2 + [Math.atan2 [y1 - lastKnot.y] [x1 - lastKnot.x]]
if [lastKnot.normalAngle === nothing] : set lastKnot.normalAngle normalAngle
}
if [not unimportant] : begin {
local normalAngle : Math.PI / 2 + [Math.atan2 [y - y2] [x - x2]]
set thisKnot.normalAngle normalAngle
this.controlKnots.push thisKnot
}
}
define [SpiroExpansionContext.prototype.set-width l r] : begin {
local lastKnot this.controlKnots.[this.controlKnots.length - 1]
if lastKnot {
then { lastKnot.d1 = l; lastKnot.d2 = r }
else { this.defaultd1 = l; this.defaultd2 = r}
}
}
define [SpiroExpansionContext.prototype.heads-to direction] : begin {
local lastKnot this.controlKnots.[this.controlKnots.length - 1]
if lastKnot : begin {
lastKnot.proposedNormal = direction
}
}
define [SpiroExpansionContext.prototype.set-type type] : begin {
local lastKnot this.controlKnots.[this.controlKnots.length - 1]
if lastKnot : begin {
lastKnot.type = type
}
}
define [shortestAngle end start] : begin {
local a : [end - start] % [Math.PI * 2]
if [a > Math.PI / 2] : a = a - Math.PI
return a
}
define [SpiroExpansionContext.prototype.expand] : begin {
local lhs ()
local rhs ()
local d1s ()
local d2s ()
local dxs ()
local dys ()
local js ()
foreach j [range 0 this.controlKnots.length] : if [not this.controlKnots.(j).unimportant] : begin {
local knot this.controlKnots.(j)
js.push j
d1s.push knot.d1
d2s.push knot.d2
if knot.proposedNormal {
then {
dxs.push : knot.proposedNormal.x - [Math.cos knot.normalAngle]
dys.push : knot.proposedNormal.y - [Math.sin knot.normalAngle]
}
else { dxs.push 0; dys.push 0 }
}
}
local fd1 : smooth js d1s
local fd2 : smooth js d2s
local fdx : smooth js dxs
local fdy : smooth js dys
# console.log deltaAngles
# interpolate important knots
foreach j [range 0 this.controlKnots.length] : begin {
local knot this.controlKnots.(j)
if [not knot.unimportant] : begin {
set lhs.(j) : object {
x : knot.x + [[fdx j] + [Math.cos knot.normalAngle]] * [fd1 j]
y : knot.y + [[fdy j] + [Math.sin knot.normalAngle]] * [fd1 j]
type knot.type
}
set rhs.(j) : object {
x : knot.x - [[fdx j] + [Math.cos knot.normalAngle]] * [fd2 j]
y : knot.y - [[fdy j] + [Math.sin knot.normalAngle]] * [fd2 j]
type : match knot.type {
"left" "right"
"right" "left"
type type
}
}
}
}
# interpolate unimportant knots referencing their original position relationship
foreach j [range 0 this.controlKnots.length] : begin {
local knot this.controlKnots.(j)
if knot.unimportant : begin {
local jBefore [j - 1]
while this.controlKnots.(jBefore).unimportant : dec jBefore
local jAfter [j + 1]
while this.controlKnots.(jAfter).unimportant : inc jAfter
local knotBefore : utp this.gizmo this.controlKnots.(jBefore)
local knotAfter : utp this.gizmo this.controlKnots.(jAfter)
local ref : utp this.gizmo knot
local lhsBefore : utp this.gizmo lhs.(jBefore)
local lhsAfter : utp this.gizmo lhs.(jAfter)
local rhsBefore : utp this.gizmo rhs.(jBefore)
local rhsAfter : utp this.gizmo rhs.(jAfter)
local kLHS : tp this.gizmo : object {
x : linreg knotBefore.x lhsBefore.x knotAfter.x lhsAfter.x ref.x
y : linreg knotBefore.y lhsBefore.y knotAfter.y lhsAfter.y ref.y
}
local kRHS : tp this.gizmo : object {
x : linreg knotBefore.x rhsBefore.x knotAfter.x rhsAfter.x ref.x
y : linreg knotBefore.y rhsBefore.y knotAfter.y rhsAfter.y ref.y
}
set lhs.(j) : object {
x kLHS.x
y kLHS.y
type knot.type
}
set rhs.(j) : object {
x kRHS.x
y kRHS.y
type : match knot.type {
"left" "right"
"right" "left"
type type
}
}
}
}
lhs.0.type = rhs.0.type = lhs.[lhs.length - 1].type = rhs.[rhs.length - 1].type = 'corner'
return : lhs.concat [rhs.reverse]
}
exports.SpiroExpansionContext = SpiroExpansionContext