Added a new stroke expansion mechanism, spiroexpand. How we finally have a 'S' looking good.

This commit is contained in:
be5invis 2015-08-19 01:51:56 +08:00
parent be865f5ddb
commit 6fa6a8ce85
12 changed files with 271 additions and 34 deletions

1
.gitignore vendored
View file

@ -44,5 +44,6 @@ buildglyphs.js
parameters.js parameters.js
support/glyph.js support/glyph.js
support/stroke.js support/stroke.js
support/spiroexpand.js
testdrive/*.ttf testdrive/*.ttf
testdrive/*.charmap testdrive/*.charmap

View file

@ -2,9 +2,8 @@ define Glyph [require './support/glyph'].Glyph
define Stroke [require './support/stroke'].Stroke define Stroke [require './support/stroke'].Stroke
define tp [require './support/transform'].transformPoint define tp [require './support/transform'].transformPoint
define inverse [require './support/transform'].inverse define inverse [require './support/transform'].inverse
define libspiro : require 'libspiro-js' define libspiro : require 'libspiro-js'
define SpiroExpansionContext [require './support/spiroexpand'].SpiroExpansionContext
### COMMON FUNCTIONS ### COMMON FUNCTIONS
define [mix a b p] : a + [b - a] * p define [mix a b p] : a + [b - a] * p
@ -333,12 +332,23 @@ define [buildFont para recursive] : begin {
} }
return a return a
} }
define [unimportant] : if [this.points && this.points.length && this.points.[this.points.length - 1]] : this.points.[this.points.length - 1].subdivided = true define [unimportant] : begin {
define [afInterpolate before after args] : g2 [mix before.x after.x args.rx] [mix before.y after.y args.ry] unimportant if [this.points && this.points.length && this.points.[this.points.length - 1]] : this.points.[this.points.length - 1].subdivided = true
if [this.controlKnots && this.controlKnots.length && this.controlKnots.[this.controlKnots.length - 1]] : this.controlKnots.[this.controlKnots.length - 1].unimportant = true
}
define [afInterpolate before after args] : g2 {
mix before.x after.x args.rx
mix before.y after.y args.ry
fallback args.raf unimportant
}
define [bez3 a b c d t] : [1 - t] * [1 - t] * [1 - t] * a + 3 * [1 - t] * [1 - t] * t * b +3 * t * t * [1 - t] * c + t * t * t * d define [bez3 a b c d t] : [1 - t] * [1 - t] * [1 - t] * a + 3 * [1 - t] * [1 - t] * t * b +3 * t * t * [1 - t] * c + t * t * t * d
define [afInterpolateThem before after args] : begin { define [afInterpolateThem before after args] : begin {
local knots () local knots ()
foreach (rx ry) [items-of args.rs] : knots.push : g2 [mix before.x after.x rx] [mix before.y after.y ry] unimportant foreach (rx ry preserve) [items-of args.rs] : knots.push : g2 [mix before.x after.x rx] [mix before.y after.y ry] : match preserve {
1 before.af
2 after.af
otherwise unimportant
}
return knots return knots
} }
@ -358,18 +368,18 @@ define [buildFont para recursive] : begin {
define [widths.heading l r d] : lambda [] : begin { this.set-width l r; this.heads-to d } define [widths.heading l r d] : lambda [] : begin { this.set-width l r; this.heads-to d }
define [heading d] : lambda [] : this.heads-to d define [heading d] : lambda [] : this.heads-to d
define [alsothru rx ry] (.type 'interpolate' .rx rx .ry ry .af afInterpolate) define [alsothru rx ry raf] (.type 'interpolate' .rx rx .ry ry .raf raf .af afInterpolate)
define [alsothruthem rs] (.type 'interpolate' .rs rs .af afInterpolateThem) define [alsothruthem rs] (.type 'interpolate' .rs rs .af afInterpolateThem)
define [bezcontrols x1 y1 x2 y2 _samples notiny] : begin { define [bezcontrols x1 y1 x2 y2 _samples notiny] : begin {
local samples : fallback _samples 2 local samples : fallback _samples 5
local tiny 0.005 local tiny 0.005
local rs () local rs ()
if [not notiny] : rs.push ([bez3 0 x1 x2 1 tiny] [bez3 0 y1 y2 1 tiny]) # if [not notiny] : rs.push ([bez3 0 x1 x2 1 tiny] [bez3 0 y1 y2 1 tiny] 1)
foreach j [range 1 samples] : rs.push : list { foreach j [range 1 samples] : rs.push : list {
bez3 0 x1 x2 1 [mix tiny [1 - tiny] [j / samples]] bez3 0 x1 x2 1 [mix tiny [1 - tiny] [j / samples]]
bez3 0 y1 y2 1 [mix tiny [1 - tiny] [j / samples]] bez3 0 y1 y2 1 [mix tiny [1 - tiny] [j / samples]]
} }
if [not notiny] : rs.push ([bez3 0 x1 x2 1 [1 - tiny]] [bez3 0 y1 y2 1 [1 - tiny]]) # if [not notiny] : rs.push ([bez3 0 x1 x2 1 [1 - tiny]] [bez3 0 y1 y2 1 [1 - tiny]] 2)
alsothruthem rs alsothruthem rs
} }
define [quadcontrols x1 y1 samples] : bezcontrols [x1 * 2 / 3] [y1 * 2 / 3] [mix 1 x1 [2 / 3]] [mix 1 y1 [2 / 3]] samples define [quadcontrols x1 y1 samples] : bezcontrols [x1 * 2 / 3] [y1 * 2 / 3] [mix 1 x1 [2 / 3]] [mix 1 y1 [2 / 3]] samples
@ -404,7 +414,7 @@ define [buildFont para recursive] : begin {
if closed : knots.pop if closed : knots.pop
return (.knots [flatten knots] .closed closed .lastafs lastafs) return (.knots [flatten knots] .closed closed .lastafs lastafs)
} }
define [spiro] : begin { define [spiro-stroke] : begin {
local s : new Stroke local s : new Stroke
s.set-transform globalTransform s.set-transform globalTransform
s.set-samples 1 s.set-samples 1
@ -413,6 +423,24 @@ define [buildFont para recursive] : begin {
foreach af [items-of lastafs] : if af : af.call s foreach af [items-of lastafs] : if af : af.call s
return s return s
} }
define [spiro] : begin {
local s : new SpiroExpansionContext
set s.gizmo globalTransform
local (.knots knots .closed closed .lastafs lastafs) : prepareSpiroKnots [().slice.call arguments 0] s
foreach knot [items-of knots] : let [ty knot.type] [af knot.af] : begin {
set knot.af : lambda [] : begin {
this.set-type ty
if af : af.apply this arguments
}
}
libspiro.spiroToBezierOnContext knots closed s
foreach af [items-of lastafs] : if af : af.call s
local outlineKnots : s.expand
local g : new Glyph
libspiro.spiroToBezierOnContext outlineKnots true g
# g.cleanup
return g.contours
}
define [spiro-outline] : let [k : ().slice.call arguments 0] : glyph-construction { define [spiro-outline] : let [k : ().slice.call arguments 0] : glyph-construction {
local (.knots knots .closed closed .lastafs lastafs) : prepareSpiroKnots k this local (.knots knots .closed closed .lastafs lastafs) : prepareSpiroKnots k this
libspiro.spiroToBezierOnContext knots closed this libspiro.spiroToBezierOnContext knots closed this

View file

@ -172,11 +172,14 @@ define [nShoulder left middle right fine _top _bottom _sma _smb _wide] : glyph-c
curl right [top - smb] curl right [top - smb]
arcvh arcvh
g4 middle [top - O] [heading LEFTWARD] g4 middle [top - O] [heading LEFTWARD]
archv
flat left [top - sma] [widths 0 fine]
curl left [top - sma - 2]
} }
start-from middle [top - O - stroke] # start-from middle [top - O - stroke]
arc-hv-to left [top - sma] # arc-hv-to left [top - sma]
line-to [left - fine] [top - sma] # line-to [left - fine] [top - sma]
arc-vh-to middle [top - O] # arc-vh-to middle [top - O]
} }
define [mShoulderSpiro left right top bottom width fine] : glyph-construction { define [mShoulderSpiro left right top bottom width fine] : glyph-construction {

View file

@ -223,6 +223,7 @@ create-glyph 'B' : glyph-construction {
assign-unicode 'B' assign-unicode 'B'
include capitalMarks include capitalMarks
include : BShape CAP include : BShape CAP
#throw 'www'
} }
create-glyph 'D' : glyph-construction { create-glyph 'D' : glyph-construction {
@ -369,7 +370,6 @@ create-glyph 'U' : glyph-construction {
set-width WIDTH set-width WIDTH
assign-unicode 'U' assign-unicode 'U'
include capitalMarks include capitalMarks
include : UShape CAP 0 include : UShape CAP 0
} }
@ -599,7 +599,7 @@ create-glyph 'S' : glyph-construction {
g4 RIGHTSB [CAP - HOOK] g4 RIGHTSB [CAP - HOOK]
hookstart CAPO hookstart CAPO
g4 SB [CAP - SMOOTHA] g4 SB [CAP - SMOOTHA]
sband alsothru 0.5 0.5 [widths HALFSTROKE HALFSTROKE]
g4 RIGHTSB SMOOTHA [widths 0 STROKE] g4 RIGHTSB SMOOTHA [widths 0 STROKE]
hookend O hookend O
g4 SB HOOK g4 SB HOOK

View file

@ -9,11 +9,20 @@ create-glyph 'o' : glyph-construction {
include : smallo XH 0 SB RIGHTSB include : smallo XH 0 SB RIGHTSB
} }
define [oLeft] : glyph-construction { define [oLeft] : glyph-construction {
include : list { include : spiro {
ORing XO O [SB + HALFSTROKE] [RIGHTSB - O] SMALLSMOOTHA SMALLSMOOTHB 0 widths.lhs
ORing [XO - STROKE] [O + STROKE] [SB + STROKE * ITALICCOR] [RIGHTSB - STROKE * ITALICCOR - O] [SMALLSMOOTHA - STROKE] [SMALLSMOOTHB - STROKE] 0 g4 [MIDDLE - OMIDCOR_S + HALFSTROKE / 4] XO
archv
flat [SB + HALFSTROKE * ITALICCOR] [XH - SMALLSMOOTHA] [widths HALFSTROKE 0]
curl [SB + HALFSTROKE * ITALICCOR] [0 + SMALLSMOOTHB] [widths HALFSTROKE 0]
arcvh
g4 [MIDDLE + OMIDCOR_S + HALFSTROKE / 4] O [widths STROKE 0]
archv
flat [RIGHTSB - O] [0 + SMALLSMOOTHA]
curl [RIGHTSB - O] [XH - SMALLSMOOTHB]
arcvh
close
} }
reverse-last
} }
define [oRight] : glyph-construction { define [oRight] : glyph-construction {
include : create-glyph [oLeft] include : create-glyph [oLeft]
@ -153,7 +162,7 @@ create-glyph 'e.italic' : glyph-construction {
local barbottom [XH * EBARPOS] local barbottom [XH * EBARPOS]
include : spiro { include : spiro-stroke {
widths.lhs widths.lhs
g4 [SB + O + STROKE] barbottom g4 [SB + O + STROKE] barbottom
archv 8 archv 8
@ -628,11 +637,13 @@ create-glyph 's' : glyph-construction {
g4 RIGHTSB [XH - SHOOK] g4 RIGHTSB [XH - SHOOK]
hookstart XO SBALANCE hookstart XO SBALANCE
g4 SB [XH - SMOOTHA * 0.85] g4 SB [XH - SMOOTHA * 0.85]
sband STROKE false 1 0.75 [linreg 75 1 120 0.5 STROKE] alsothru 0.5 0.5 [widths HALFSTROKE HALFSTROKE]
#sband STROKE false 1 0.75 [linreg 75 1 120 0.55 STROKE]
g4 RIGHTSB [SMOOTHA * 0.85] [widths 0 STROKE] g4 RIGHTSB [SMOOTHA * 0.85] [widths 0 STROKE]
hookend O SBALANCE hookend O SBALANCE
g4 SB SHOOK g4 SB SHOOK
} }
# throw 'w'
} }
### r ### r
@ -648,7 +659,7 @@ create-glyph 'r' : glyph-construction {
widths.rhs widths.rhs
g4 rhookx [XH - RHOOK - STROKE * 0.5] g4 rhookx [XH - RHOOK - STROKE * 0.5]
g4 [mix barright rhookx 0.52] [XO - STROKE] [heading LEFTWARD] g4 [mix barright rhookx 0.52] [XO - STROKE] [heading LEFTWARD]
archv archv 8 'notiny'
flat barright [XH - SMALLSMOOTHA] [widths 0 [STROKE * 0.3]] flat barright [XH - SMALLSMOOTHA] [widths 0 [STROKE * 0.3]]
curl barright [XH - SMALLSMOOTHA - 1] curl barright [XH - SMALLSMOOTHA - 1]
} }

View file

@ -57,13 +57,13 @@ create-glyph 'two' : glyph-construction {
g4 [[mix RIGHTSB SB SBALANCE] - OMIDCOR_S] CAPO g4 [[mix RIGHTSB SB SBALANCE] - OMIDCOR_S] CAPO
archv 4 archv 4
g4 RIGHTSB [CAP - smb] g4 RIGHTSB [CAP - smb]
alsothruthem : list (0.5 [0.475 + 0.4 * HALFSTROKE / [CAP - smb - STROKE]]) alsothru 0.5 0.45 [widths HALFSTROKE HALFSTROKE]
flat [SB + STROKE * ITALICCOR] STROKE flat SB 1 [widths.heading STROKE 0 DOWNWARD]
curl [SB + STROKE * ITALICCOR] HALFSTROKE curl SB 0 [heading DOWNWARD]
} }
include : create-stroke include : create-stroke
:.start-from SB 0 :.start-from [SB + HALFSTROKE] 0
:.set-width STROKE 0 :.set-width STROKE 0
:.heads-to RIGHTWARD :.heads-to RIGHTWARD
:.line-to RIGHTSB 0 :.line-to RIGHTSB 0
@ -83,16 +83,18 @@ create-glyph 'three' : glyph-construction {
g4 SB [CAP - HOOK] g4 SB [CAP - HOOK]
hookstart CAPO hookstart CAPO
g4 RIGHTSB [CAP - [SMOOTHB * [CAP - barcenter] / CAPMIDDLE]] g4 RIGHTSB [CAP - [SMOOTHB * [CAP - barcenter] / CAPMIDDLE]]
arcvh # arcvh
flat [RIGHTSB - threeRadius] [barcenter - HALFSTROKE] [heading LEFTWARD] flat [RIGHTSB - threeRadius] [barcenter - HALFSTROKE] [heading LEFTWARD]
curl [RIGHTSB - threeRadius - 1] [barcenter - HALFSTROKE] [heading LEFTWARD]
} }
include : spiro { include : spiro {
widths.lhs widths.lhs
g4 SB HOOK g4 SB HOOK
hookstart O hookstart O
g4 RIGHTSB [SMOOTHA * barcenter / CAPMIDDLE] g4 RIGHTSB [SMOOTHA * barcenter / CAPMIDDLE]
arcvh # arcvh
flat [RIGHTSB - threeRadius] [barcenter + HALFSTROKE] [heading LEFTWARD] flat [RIGHTSB - threeRadius] [barcenter + HALFSTROKE] [heading LEFTWARD]
curl [RIGHTSB - threeRadius - 1] [barcenter + HALFSTROKE] [heading LEFTWARD]
} }
} }
@ -191,11 +193,13 @@ create-glyph 'eight' : glyph-construction {
g4 [MIDDLE - OMIDCOR_S + ITALICCORS] [CAP - O - STROKE] g4 [MIDDLE - OMIDCOR_S + ITALICCORS] [CAP - O - STROKE]
archv 1 archv 1
g4 [[mix SB RIGHTSB p] - STROKE] [CAP - smb * p] g4 [[mix SB RIGHTSB p] - STROKE] [CAP - smb * p]
alsothru 0.5 0.5 [widths HALFSTROKE HALFSTROKE]
g4 [SB + STROKE] smb [widths 0 STROKE] g4 [SB + STROKE] smb [widths 0 STROKE]
arcvh 1 arcvh 1
g4 [MIDDLE + OMIDCOR_S - ITALICCORS] [O + STROKE] g4 [MIDDLE + OMIDCOR_S - ITALICCORS] [O + STROKE]
archv 1 archv 1
g4 [RIGHTSB - STROKE] sma [widths 0 STROKE] g4 [RIGHTSB - STROKE] sma [widths 0 STROKE]
alsothru 0.5 0.5 [widths HALFSTROKE HALFSTROKE]
g4 [[mix RIGHTSB SB p] + STROKE] [CAP - sma * p] [widths STROKE 0] g4 [[mix RIGHTSB SB p] + STROKE] [CAP - sma * p] [widths STROKE 0]
arcvh 1 arcvh 1
close close

View file

@ -36,11 +36,13 @@ create-glyph 'ampersand' : glyph-construction {
g4 MIDDLE [O + fine] [widths fine 0] g4 MIDDLE [O + fine] [widths fine 0]
archv archv
g4 [SB + O + fine * ITALICCOR] [SMOOTHB - ITALICCOR * ITALICCORS] g4 [SB + O + fine * ITALICCOR] [SMOOTHB - ITALICCOR * ITALICCORS]
alsothru 0.5 0.5 [widths [fine / 2] [fine / 2]]
g4 [[mix SB RIGHTSB p] - fine * ITALICCOR] [CAP - SMOOTHB * pr + ITALICCOR * ITALICCORS * [fine / STROKE]] [widths 0 fine] g4 [[mix SB RIGHTSB p] - fine * ITALICCOR] [CAP - SMOOTHB * pr + ITALICCOR * ITALICCORS * [fine / STROKE]] [widths 0 fine]
arcvh arcvh
g4 [[mix SB RIGHTSB [mix p l 0.5]] - OMIDCOR_S + ITALICCORS] [CAPO - fine] g4 [[mix SB RIGHTSB [mix p l 0.5]] - OMIDCOR_S + ITALICCORS] [CAPO - fine]
archv archv
g4 [[mix SB RIGHTSB l] + fine * ITALICCOR] [CAP - SMOOTHA * pr - ITALICCOR * ITALICCORS * [fine / STROKE]] g4 [[mix SB RIGHTSB l] + fine * ITALICCOR] [CAP - SMOOTHA * pr - ITALICCOR * ITALICCORS * [fine / STROKE]]
alsothru 0.5 0.45 [lambda]
flat [mix SB RIGHTSB r] [SMOOTHA * s] flat [mix SB RIGHTSB r] [SMOOTHA * s]
curl [mix SB RIGHTSB r] [[SMOOTHA * s] - 1] curl [mix SB RIGHTSB r] [[SMOOTHA * s] - 1]
} }

View file

@ -1,4 +1,4 @@
SUPPORT_FILES = support/glyph.js support/stroke.js parameters.js generate.js empty.json SUPPORT_FILES = support/glyph.js support/stroke.js support/spiroexpand.js parameters.js generate.js empty.json
GLYPH_SEGMENTS = glyphs/common-shapes.patel glyphs/overmarks.patel glyphs/latin-basic-capital.patel glyphs/latin-basic-lower.patel glyphs/greek.patel glyphs/cyrillic-basic.patel glyphs/latin-extend-basis.patel glyphs/latin-extend-decorated.patel glyphs/cyrillic-extended.patel glyphs/numbers.patel glyphs/symbol-ascii.patel glyphs/symbol-punctuation.patel glyphs/symbol-math.patel glyphs/symbol-geometric.patel glyphs/symbol-other.patel glyphs/symbol-letter.patel glyphs/autobuilds.patel GLYPH_SEGMENTS = glyphs/common-shapes.patel glyphs/overmarks.patel glyphs/latin-basic-capital.patel glyphs/latin-basic-lower.patel glyphs/greek.patel glyphs/cyrillic-basic.patel glyphs/latin-extend-basis.patel glyphs/latin-extend-decorated.patel glyphs/cyrillic-extended.patel glyphs/numbers.patel glyphs/symbol-ascii.patel glyphs/symbol-punctuation.patel glyphs/symbol-math.patel glyphs/symbol-geometric.patel glyphs/symbol-other.patel glyphs/symbol-letter.patel glyphs/autobuilds.patel
OBJDIR = build OBJDIR = build
@ -51,6 +51,7 @@ buildglyphs.js : buildglyphs.patel $(GLYPH_SEGMENTS)
patel-c --strict $< -o $@ patel-c --strict $< -o $@
support/glyph.js : support/glyph.patel support/glyph.js : support/glyph.patel
support/stroke.js : support/stroke.patel support/stroke.js : support/stroke.patel
support/spiroexpand.js : support/spiroexpand.patel
parameters.js : parameters.patel parameters.js : parameters.patel
$(OBJDIR) : $(OBJDIR) :

View file

@ -22,7 +22,7 @@ define regular (
.hookx 170 .hookx 170
.smooth 195 .smooth 195
.smallsmooth 242 .smallsmooth 230
.smoothadjust 180 .smoothadjust 180
.o [-8] .o [-8]

View file

@ -6,8 +6,9 @@ font = fontforge.open(source)
font.selection.all() font.selection.all()
font.removeOverlap() font.removeOverlap()
font.addExtrema()
font.simplify(1) font.simplify(1)
font.em = 1000
font.addExtrema()
font.canonicalContours() font.canonicalContours()
font.canonicalStart() font.canonicalStart()
font.generate(sys.argv[2], flags = ("short-post", "opentype")) font.generate(sys.argv[2], flags = ("short-post", "opentype"))

View file

@ -188,5 +188,24 @@ define [Glyph.prototype.set-anchor id type x y mbx mby] : begin {
local markbasepoint : if [mbx !== nothing && mby !== nothing] [tp this.gizmo (.x mbx .y mby)] (.x nothing .y nothing) local markbasepoint : if [mbx !== nothing && mby !== nothing] [tp this.gizmo (.x mbx .y mby)] (.x nothing .y nothing)
this.anchors`id = (.x anchorpoint.x .y anchorpoint.y .type type .mbx markbasepoint.x .mby markbasepoint.y) this.anchors`id = (.x anchorpoint.x .y anchorpoint.y .type type .mbx markbasepoint.x .mby markbasepoint.y)
} }
define [oncurveRemovable a b c] : begin {
local xm : [a.x + c.x] / 2
local ym : [a.y + c.y] / 2
return : [not a.onCurve] && b.onCurve && [not c.onCurve] && [a.x <= b.x && b.x <= c.x || a.x >= b.x && b.x >= c.x] && [a.y <= b.y && b.y <= c.y || a.y >= b.y && b.y >= c.y] && [Math.abs [b.x - xm]] <= 0.5 && [Math.abs [b.y - ym]] <= 0.5
}
define [Glyph.prototype.cleanup] : begin {
foreach c [range 0 this.contours.length] : begin {
local contour this.contours.(c)
local cleanedContour ()
foreach j [range 1 : contour.length - 1] : begin {
local p0 contour.[j - 1]
local p1 contour.(j)
local p2 contour.[j + 1]
if [oncurveRemovable p0 p1 p2] : set p1.unimportant true
}
foreach point [items-of contour] : if [not point.unimportant] : cleanedContour.push point
this.contours.(c) = cleanedContour
}
return this
}
exports.Glyph = Glyph exports.Glyph = Glyph

167
support/spiroexpand.patel Normal file
View file

@ -0,0 +1,167 @@
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