diff --git a/.gitignore b/.gitignore index 870e213ee..727d09191 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,6 @@ buildglyphs.js parameters.js support/glyph.js support/stroke.js +support/spiroexpand.js testdrive/*.ttf testdrive/*.charmap \ No newline at end of file diff --git a/buildglyphs.patel b/buildglyphs.patel index d93575e16..45c58c876 100644 --- a/buildglyphs.patel +++ b/buildglyphs.patel @@ -2,9 +2,8 @@ define Glyph [require './support/glyph'].Glyph define Stroke [require './support/stroke'].Stroke define tp [require './support/transform'].transformPoint define inverse [require './support/transform'].inverse - define libspiro : require 'libspiro-js' - +define SpiroExpansionContext [require './support/spiroexpand'].SpiroExpansionContext ### COMMON FUNCTIONS define [mix a b p] : a + [b - a] * p @@ -333,12 +332,23 @@ define [buildFont para recursive] : begin { } 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 [afInterpolate before after args] : g2 [mix before.x after.x args.rx] [mix before.y after.y args.ry] unimportant + define [unimportant] : begin { + 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 [afInterpolateThem before after args] : begin { 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 } @@ -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 [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 [bezcontrols x1 y1 x2 y2 _samples notiny] : begin { - local samples : fallback _samples 2 + local samples : fallback _samples 5 local tiny 0.005 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 { bez3 0 x1 x2 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 } 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 return (.knots [flatten knots] .closed closed .lastafs lastafs) } - define [spiro] : begin { + define [spiro-stroke] : begin { local s : new Stroke s.set-transform globalTransform s.set-samples 1 @@ -413,6 +423,24 @@ define [buildFont para recursive] : begin { foreach af [items-of lastafs] : if af : af.call 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 { local (.knots knots .closed closed .lastafs lastafs) : prepareSpiroKnots k this libspiro.spiroToBezierOnContext knots closed this diff --git a/glyphs/common-shapes.patel b/glyphs/common-shapes.patel index 4527cd101..2094399f2 100644 --- a/glyphs/common-shapes.patel +++ b/glyphs/common-shapes.patel @@ -171,12 +171,15 @@ define [nShoulder left middle right fine _top _bottom _sma _smb _wide] : glyph-c flat right bottom [heading UPWARD] curl right [top - smb] 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] - arc-hv-to left [top - sma] - line-to [left - fine] [top - sma] - arc-vh-to middle [top - O] +# start-from middle [top - O - stroke] +# arc-hv-to left [top - sma] +# line-to [left - fine] [top - sma] +# arc-vh-to middle [top - O] } define [mShoulderSpiro left right top bottom width fine] : glyph-construction { diff --git a/glyphs/latin-basic-capital.patel b/glyphs/latin-basic-capital.patel index 0026d9597..5c3a358d9 100644 --- a/glyphs/latin-basic-capital.patel +++ b/glyphs/latin-basic-capital.patel @@ -223,6 +223,7 @@ create-glyph 'B' : glyph-construction { assign-unicode 'B' include capitalMarks include : BShape CAP + #throw 'www' } create-glyph 'D' : glyph-construction { @@ -369,7 +370,6 @@ create-glyph 'U' : glyph-construction { set-width WIDTH assign-unicode 'U' include capitalMarks - include : UShape CAP 0 } @@ -599,7 +599,7 @@ create-glyph 'S' : glyph-construction { g4 RIGHTSB [CAP - HOOK] hookstart CAPO g4 SB [CAP - SMOOTHA] - sband + alsothru 0.5 0.5 [widths HALFSTROKE HALFSTROKE] g4 RIGHTSB SMOOTHA [widths 0 STROKE] hookend O g4 SB HOOK diff --git a/glyphs/latin-basic-lower.patel b/glyphs/latin-basic-lower.patel index a7bdae78e..722d9d364 100644 --- a/glyphs/latin-basic-lower.patel +++ b/glyphs/latin-basic-lower.patel @@ -9,11 +9,20 @@ create-glyph 'o' : glyph-construction { include : smallo XH 0 SB RIGHTSB } define [oLeft] : glyph-construction { - include : list { - ORing XO O [SB + HALFSTROKE] [RIGHTSB - O] SMALLSMOOTHA SMALLSMOOTHB 0 - ORing [XO - STROKE] [O + STROKE] [SB + STROKE * ITALICCOR] [RIGHTSB - STROKE * ITALICCOR - O] [SMALLSMOOTHA - STROKE] [SMALLSMOOTHB - STROKE] 0 + include : spiro { + widths.lhs + 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 { include : create-glyph [oLeft] @@ -153,7 +162,7 @@ create-glyph 'e.italic' : glyph-construction { local barbottom [XH * EBARPOS] - include : spiro { + include : spiro-stroke { widths.lhs g4 [SB + O + STROKE] barbottom archv 8 @@ -628,11 +637,13 @@ create-glyph 's' : glyph-construction { g4 RIGHTSB [XH - SHOOK] hookstart XO SBALANCE 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] hookend O SBALANCE g4 SB SHOOK } +# throw 'w' } ### r @@ -648,7 +659,7 @@ create-glyph 'r' : glyph-construction { widths.rhs g4 rhookx [XH - RHOOK - STROKE * 0.5] g4 [mix barright rhookx 0.52] [XO - STROKE] [heading LEFTWARD] - archv + archv 8 'notiny' flat barright [XH - SMALLSMOOTHA] [widths 0 [STROKE * 0.3]] curl barright [XH - SMALLSMOOTHA - 1] } diff --git a/glyphs/numbers.patel b/glyphs/numbers.patel index c1d2c1583..c57eb584a 100644 --- a/glyphs/numbers.patel +++ b/glyphs/numbers.patel @@ -57,13 +57,13 @@ create-glyph 'two' : glyph-construction { g4 [[mix RIGHTSB SB SBALANCE] - OMIDCOR_S] CAPO archv 4 g4 RIGHTSB [CAP - smb] - alsothruthem : list (0.5 [0.475 + 0.4 * HALFSTROKE / [CAP - smb - STROKE]]) - flat [SB + STROKE * ITALICCOR] STROKE - curl [SB + STROKE * ITALICCOR] HALFSTROKE + alsothru 0.5 0.45 [widths HALFSTROKE HALFSTROKE] + flat SB 1 [widths.heading STROKE 0 DOWNWARD] + curl SB 0 [heading DOWNWARD] } include : create-stroke - :.start-from SB 0 + :.start-from [SB + HALFSTROKE] 0 :.set-width STROKE 0 :.heads-to RIGHTWARD :.line-to RIGHTSB 0 @@ -83,16 +83,18 @@ create-glyph 'three' : glyph-construction { g4 SB [CAP - HOOK] hookstart CAPO g4 RIGHTSB [CAP - [SMOOTHB * [CAP - barcenter] / CAPMIDDLE]] - arcvh +# arcvh flat [RIGHTSB - threeRadius] [barcenter - HALFSTROKE] [heading LEFTWARD] + curl [RIGHTSB - threeRadius - 1] [barcenter - HALFSTROKE] [heading LEFTWARD] } include : spiro { widths.lhs g4 SB HOOK hookstart O g4 RIGHTSB [SMOOTHA * barcenter / CAPMIDDLE] - arcvh +# arcvh 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] archv 1 g4 [[mix SB RIGHTSB p] - STROKE] [CAP - smb * p] + alsothru 0.5 0.5 [widths HALFSTROKE HALFSTROKE] g4 [SB + STROKE] smb [widths 0 STROKE] arcvh 1 g4 [MIDDLE + OMIDCOR_S - ITALICCORS] [O + STROKE] archv 1 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] arcvh 1 close diff --git a/glyphs/symbol-ascii.patel b/glyphs/symbol-ascii.patel index ddc1a6287..ff7d08542 100644 --- a/glyphs/symbol-ascii.patel +++ b/glyphs/symbol-ascii.patel @@ -36,11 +36,13 @@ create-glyph 'ampersand' : glyph-construction { g4 MIDDLE [O + fine] [widths fine 0] archv 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] arcvh g4 [[mix SB RIGHTSB [mix p l 0.5]] - OMIDCOR_S + ITALICCORS] [CAPO - fine] archv 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] curl [mix SB RIGHTSB r] [[SMOOTHA * s] - 1] } diff --git a/makefile b/makefile index 4dc922dff..7c473db12 100644 --- a/makefile +++ b/makefile @@ -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 OBJDIR = build @@ -51,6 +51,7 @@ buildglyphs.js : buildglyphs.patel $(GLYPH_SEGMENTS) patel-c --strict $< -o $@ support/glyph.js : support/glyph.patel support/stroke.js : support/stroke.patel +support/spiroexpand.js : support/spiroexpand.patel parameters.js : parameters.patel $(OBJDIR) : diff --git a/parameters.patel b/parameters.patel index bbece7737..ab97185a4 100644 --- a/parameters.patel +++ b/parameters.patel @@ -22,7 +22,7 @@ define regular ( .hookx 170 .smooth 195 - .smallsmooth 242 + .smallsmooth 230 .smoothadjust 180 .o [-8] diff --git a/pass2-finalize.py b/pass2-finalize.py index 37a73a623..3209a69b5 100644 --- a/pass2-finalize.py +++ b/pass2-finalize.py @@ -6,8 +6,9 @@ font = fontforge.open(source) font.selection.all() font.removeOverlap() -font.addExtrema() font.simplify(1) +font.em = 1000 +font.addExtrema() font.canonicalContours() font.canonicalStart() font.generate(sys.argv[2], flags = ("short-post", "opentype")) \ No newline at end of file diff --git a/support/glyph.patel b/support/glyph.patel index 5bbda49a9..779eb173b 100644 --- a/support/glyph.patel +++ b/support/glyph.patel @@ -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) 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 \ No newline at end of file diff --git a/support/spiroexpand.patel b/support/spiroexpand.patel new file mode 100644 index 000000000..7a5b20a0a --- /dev/null +++ b/support/spiroexpand.patel @@ -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 \ No newline at end of file