Iosevka/support/spiroexpand.ptl
2016-03-15 17:54:27 +08:00

151 lines
5.6 KiB
Plaintext

import './monotonic-interpolate' as smooth
import './transform' as : Transform && [object [transformPoint tp] [untransform utp] inverse]
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-macro xytransform : syntax-rules
`[xytransform @tfm @x @y] : begin
set [env.declarations.get [formOf x]].isParameter 0
set [env.declarations.get [formOf y]].isParameter 0
let [t : env.newt] `[begin \\
set @t @x
set @x : @x * @tfm.xx + @y * @tfm.yx + @tfm.x
set @y : @t * @tfm.xy + @y * @tfm.yy + @tfm.y
]
define [normalY angle] : Math.sin angle
define [normalX angle vex] : [Math.cos angle] * vex
export all : class SpiroExpansionContext
public [new] : begin
set this.gizmo [Transform.Id]
set this.controlKnots {}
set this.defaultd1 0
set this.defaultd2 0
public [moveTo x y unimportant] : begin
if unimportant : return nothing
# Transform incoming knots using gizmo
xytransform this.gizmo x y
this.controlKnots.push {.x x .y y .type 'g4' .d1 this.defaultd1 .d2 this.defaultd2}
public [lineTo x y unimportant] : begin
local lastKnot this.controlKnots.(this.controlKnots.length - 1)
xytransform this.gizmo x 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
public [cubicTo x1 y1 x2 y2 x y unimportant] : begin
local lastKnot this.controlKnots.(this.controlKnots.length - 1)
xytransform this.gizmo x1 y1
xytransform this.gizmo x2 y2
xytransform this.gizmo x 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
public [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
public [heads-to direction] : begin
local lastKnot this.controlKnots.(this.controlKnots.length - 1)
if lastKnot : begin
lastKnot.proposedNormal = direction
public [set-type type] : begin
local lastKnot this.controlKnots.(this.controlKnots.length - 1)
if lastKnot : begin
lastKnot.type = type
public [expand contrast] : begin
local lhs {}
local rhs {}
local contrast : fallback contrast (1 / 0.9)
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 - [normalX knot.normalAngle contrast]
dys.push : knot.proposedNormal.y - [normalY knot.normalAngle contrast]
: 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] + [normalX knot.normalAngle contrast]) * [fd1 j]
y : knot.y + ([fdy j] + [normalY knot.normalAngle contrast]) * [fd1 j]
type knot.type
set rhs.(j) : object
x : knot.x - ([fdx j] + [normalX knot.normalAngle contrast]) * [fd2 j]
y : knot.y - ([fdy j] + [normalY knot.normalAngle contrast]) * [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
return {.lhs lhs .rhs rhs}