From c07e07ed7ee68e287c36d0e64a6d5cda8814668e Mon Sep 17 00:00:00 2001 From: be5invis Date: Thu, 4 Feb 2016 06:55:33 +0800 Subject: [PATCH] So should we have OOP? --- package.json | 2 +- support/glyph.ptl | 452 ++++++++++++++++++++-------------------- support/spiroexpand.ptl | 269 ++++++++++++------------ 3 files changed, 358 insertions(+), 365 deletions(-) diff --git a/package.json b/package.json index 4db4c6a..2b74eee 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.7.3", "main": "./generate.js", "dependencies": { - "patel": ">=0.27.0", + "patel": ">=0.27.2", "node-sfnt": ">=0.0.20", "bezier-js": "*", "yargs": "*", diff --git a/support/glyph.ptl b/support/glyph.ptl index fdb091d..cc10f6c 100644 --- a/support/glyph.ptl +++ b/support/glyph.ptl @@ -17,149 +17,6 @@ define [ratio l r m] : if [l === r] 0 ((m - l) / (r - l)) define [byx a b] : a - b define [fallback] : for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j) -export all : define [Glyph name] : begin - set this.name name - set this.unicode {} - set this.contours {} - set this.advanceWidth 500 - set this.anchors {.} - set this.gizmo : Transform.Id - set this.dependencies {} - set this.defaultTag null - return nothing - -define Glyph.is {.unapply [function [obj arity] [if (obj <@ Glyph) {obj} null]]} -define [Glyph.prototype.set-width w] : begin - this.advanceWidth = w - return this - -define [Glyph.prototype.assign-unicode u] : begin - this.unicode.push : piecewise - ([typeof u] === 'string') : u.charCodeAt 0 - true u - return this - - -define [Glyph.prototype.start-from x y] : begin - local contour {[Point.transformed this.gizmo x y true]} - set contour.tag this.defaultTag - this.contours.push contour - return this -Glyph.prototype.moveTo = Glyph.prototype.start-from - -define [Glyph.prototype.line-to x y] : begin - this.contours.((this.contours.length - 1)).push [Point.transformed this.gizmo x y true] - return this -Glyph.prototype.lineTo = Glyph.prototype.line-to - -define [Glyph.prototype.curve-control x y] : begin - this.contours.((this.contours.length - 1)).push [tp this.gizmo [new Point x y false]] - return this - -define [Glyph.prototype.curve-to xc yc x y] : begin - this.contours.((this.contours.length - 1)).push [Point.transformed this.gizmo xc yc false] [Point.transformed this.gizmo x y true] - return this -Glyph.prototype.curveTo = Glyph.prototype.curve-to - -define [Glyph.prototype.cubic-to x1 y1 x2 y2 x y] : begin - #local lastContour this.contours.(this.contours.length - 1) - #local lastPoint : utp this.gizmo lastContour.(lastContour.length - 1) - #local segments : bezierCubic2Q2 lastPoint {.x x1 .y y1} {.x x2 .y y2} {.x x .y y} - #foreach {p0 {.x xc .y yc} {.x xf .y yf}} [items-of segments] : lastContour.push [Point.transformed this.gizmo xc yc false] [Point.transformed this.gizmo xf yf true] - this.contours.((this.contours.length - 1)).push - Point.transformed this.gizmo x1 y1 false true - Point.transformed this.gizmo x2 y2 false true - Point.transformed this.gizmo x y true - return this -Glyph.prototype.cubicTo = Glyph.prototype.cubic-to - -define [Glyph.prototype.reverse-last] : begin - if [this.contours && this.contours.(this.contours.length - 1)] : begin - this.contours.(this.contours.length - 1) = [this.contours.(this.contours.length - 1).reverse] - return this -Glyph.prototype.reverseLast = Glyph.prototype.reverse-last - -define [Glyph.prototype.tag-contour tag n] : begin - if this.contours : begin - local lastContour this.contours.(this.contours.length - 1) - if lastContour : if tag : set lastContour.tag tag - return this -define [Glyph.prototype.retag-contour oldtag newtag] : begin - if this.contours : foreach [c : items-of this.contours] : if (c.tag === oldtag) : set c.tag newtag - return this -define [Glyph.prototype.eject-contour tag] : begin - set this.contours : this.contours.filter : lambda [c] (c.tag !== tag) - return this - -define [Glyph.prototype.include component copyAnchors] : begin - piecewise - (component <@ Function) : begin - local t this.defaultTag - if component.tag : set this.defaultTag component.tag - component.call this - set this.defaultTag t - return this - (component <@ Transform) : return : this.apply-transform component copyAnchors - (component <@ Array) : begin - local contours component - local glyph {.contours contours} - true : begin - local contours component.contours - local glyph component - - local shiftx 0 - local shifty 0 - local t this - if (this.anchors && glyph.anchors) : foreach markid [items-of [Object.keys this.anchors]] : begin - local anchorThis this.anchors.(markid) - local anchorThat glyph.anchors.(markid) - if ( anchorThis && (anchorThis.type === Anchor.BASE || anchorThis.mbx !== nothing && anchorThis.mby !== nothing) - && anchorThat && anchorThat.type === Anchor.MARK) : begin - set shiftx : [fallback anchorThis.mbx anchorThis.x] - anchorThat.x - set shifty : [fallback anchorThis.mby anchorThis.y] - anchorThat.y - # we have a mark-to-mark position - if (anchorThat.mbx !== nothing && anchorThat.mby !== nothing) : if (anchorThis.type === Anchor.BASE) - then : set this.anchors.(markid) : new Anchor - * (anchorThis.x + anchorThat.mbx - anchorThat.x) - * (anchorThis.y + anchorThat.mby - anchorThat.y) - * Anchor.BASE - else : set this.anchors.(markid) : new Anchor - * anchorThis.x - * anchorThis.y - * anchorThis.type - * (anchorThis.mbx + anchorThat.mbx - anchorThat.x) - * (anchorThis.mby + anchorThat.mby - anchorThat.y) - if contours : begin - local newcontours {} - foreach [contour : items-of contours] : begin - local c {} - set c.tag : contour.tag || component.tag || t.defaultTag - foreach [point : items-of contour] : begin - c.push : new Point (point.x + shiftx) (point.y + shifty) point.onCurve point.cubic point.subdivided - newcontours.push c - set this.contours : this.contours.concat newcontours - if (([not contours] || copyAnchors) && glyph.anchors) : begin - foreach [k : items-of : Object.keys glyph.anchors] : set this.anchors.(k) glyph.anchors.(k) - piecewise - glyph.name : this.dependencies.push glyph.name - glyph.dependencies : this.dependencies = [this.dependencies.concat glyph.dependencies] - return this - -define [Glyph.prototype.apply-transform transform alsoAnchors] : begin - foreach [c : items-of this.contours] : foreach [j : range 0 c.length] : set c.(j) : tp transform c.(j) - if alsoAnchors : foreach key [items-of [Object.keys this.anchors]] : begin - set this.anchors.(key) : Anchor.transform transform this.anchors.(key) - return this - -define [Glyph.prototype.set-anchor id type x y mbx mby] : begin - xytransform this.gizmo x y - if (mbx !== nothing && mby !== nothing) - : then : begin - xytransform this.gizmo mbx mby - set this.anchors.(id) : new Anchor x y type mbx mby - : else : set this.anchors.(id) : new Anchor x y type - return this - define [closepoint p q t] : begin return : [Math.abs (p.x - q.x)] <= t && [Math.abs (p.y - q.y)] <= t define [oncurveRemovable a b c t] : begin @@ -167,88 +24,231 @@ define [oncurveRemovable a b c t] : begin local ym : (a.y + c.y) / 2 return : [not a.onCurve] && b.onCurve && [not c.onCurve] && [not a.cubic] && [not c.cubic] && (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)] <= (t / 2) && [Math.abs (b.y - ym)] <= (t / 2) -define [Glyph.prototype.cleanup t] : begin - foreach c [range 0 this.contours.length] : begin - local ocontour this.contours.(c) - # add infections - local contour {[new Point ocontour.0.x ocontour.0.y ocontour.0.onCurve]} - local flag 0 - foreach [j : range 1 (ocontour.length - 1)] : piecewise - flag : dec flag - ocontour.(j).onCurve : contour.push ocontour.(j) - ocontour.(j).cubic : begin - local p0 contour.(contour.length - 1) - local p1 ocontour.(j) - local p2 ocontour.(j + 1) - local p3 ocontour.(j + 2) - local strand : new Bezier p0.x p0.y p1.x p1.y p2.x p2.y p3.x p3.y - local ts [strand.inflections].y - piecewise - (!ts || ts.length < 1) : contour.push p1 p2 p3 - true : begin - set ts [ts.sort byx] - ts.unshift 0 - ts.push 1 - foreach [k : range 0 (ts.length - 1)] : begin - local s : strand.split ts.(k) ts.(k + 1) - if s.points : contour.push - new Point s.points.1.x s.points.1.y false true - new Point s.points.2.x s.points.2.y false true - new Point s.points.3.x s.points.3.y true - set flag 2 - true : begin - local p0 contour.(contour.length - 1) - local p1 ocontour.(j) - local p2 ocontour.(j + 1) - if [not p2.onCurve] : set p2 : new Point [mix p1.x p2.x 0.5] [mix p1.y p2.y 0.5] true - local strand : new Bezier p0.x p0.y p1.x p1.y p2.x p2.y - local ts [strand.inflections].y - piecewise - (!ts || ts.length < 1) : contour.push p1 p2 - true : begin - set ts [ts.sort byx] - ts.unshift 0 - ts.push 1 - foreach [k : range 0 (ts.length - 1)] : begin - local s : strand.split ts.(k) ts.(k + 1) - if s.points : contour.push - new Point s.points.1.x s.points.1.y false - new Point s.points.2.x s.points.2.y true - set flag 1 +export all : class Glyph + public [new name] : begin + set this.name name + set this.unicode {} + set this.contours {} + set this.advanceWidth 500 + set this.anchors {.} + set this.gizmo : Transform.Id + set this.dependencies {} + set this.defaultTag null + return nothing + static is {.unapply [function [obj arity] [if (obj <@ Glyph) {obj} null]]} + + public [set-width w] : begin + this.advanceWidth = w + return this - contour.push [new Point ocontour.(ocontour.length - 1).x ocontour.(ocontour.length - 1).y ocontour.(ocontour.length - 1).onCurve] - - # cleanup - 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 t] : set p1.unimportant true - foreach point [items-of contour] : if [not point.unimportant] : cleanedContour.push point - set contour cleanedContour + public [assign-unicode u] : begin + this.unicode.push : piecewise + ([typeof u] === 'string') : u.charCodeAt 0 + true u + return this + + public [start-from x y] : begin + local contour {[Point.transformed this.gizmo x y true]} + set contour.tag this.defaultTag + this.contours.push contour + return this + public moveTo Type.prototype.start-from + + public [line-to x y] : begin + this.contours.((this.contours.length - 1)).push [Point.transformed this.gizmo x y true] + return this + public lineTo Type.prototype.line-to + + public [curve-control x y] : begin + this.contours.((this.contours.length - 1)).push [tp this.gizmo [new Point x y false]] + return this + + public [curve-to xc yc x y] : begin + this.contours.((this.contours.length - 1)).push [Point.transformed this.gizmo xc yc false] [Point.transformed this.gizmo x y true] + return this + public curveTo Type.prototype.curve-to + + public [cubic-to x1 y1 x2 y2 x y] : begin + #local lastContour this.contours.(this.contours.length - 1) + #local lastPoint : utp this.gizmo lastContour.(lastContour.length - 1) + #local segments : bezierCubic2Q2 lastPoint {.x x1 .y y1} {.x x2 .y y2} {.x x .y y} + #foreach {p0 {.x xc .y yc} {.x xf .y yf}} [items-of segments] : lastContour.push [Point.transformed this.gizmo xc yc false] [Point.transformed this.gizmo xf yf true] + this.contours.((this.contours.length - 1)).push + Point.transformed this.gizmo x1 y1 false true + Point.transformed this.gizmo x2 y2 false true + Point.transformed this.gizmo x y true + return this + public cubicTo Type.prototype.cubic-to + + public [reverse-last] : begin + if [this.contours && this.contours.(this.contours.length - 1)] : begin + this.contours.(this.contours.length - 1) = [this.contours.(this.contours.length - 1).reverse] + return this + public reverseLast Type.prototype.reverse-last + + public [tag-contour tag n] : begin + if this.contours : begin + local lastContour this.contours.(this.contours.length - 1) + if lastContour : if tag : set lastContour.tag tag + return this + public [retag-contour oldtag newtag] : begin + if this.contours : foreach [c : items-of this.contours] : if (c.tag === oldtag) : set c.tag newtag + return this + public [eject-contour tag] : begin + set this.contours : this.contours.filter : lambda [c] (c.tag !== tag) + return this + + public [include component copyAnchors] : begin + piecewise + (component <@ Function) : begin + local t this.defaultTag + if component.tag : set this.defaultTag component.tag + component.call this + set this.defaultTag t + return this + (component <@ Transform) : return : this.apply-transform component copyAnchors + (component <@ Array) : begin + local contours component + local glyph {.contours contours} + true : begin + local contours component.contours + local glyph component + + local shiftx 0 + local shifty 0 + local t this + if (this.anchors && glyph.anchors) : foreach markid [items-of [Object.keys this.anchors]] : begin + local anchorThis this.anchors.(markid) + local anchorThat glyph.anchors.(markid) + if ( anchorThis && (anchorThis.type === Anchor.BASE || anchorThis.mbx !== nothing && anchorThis.mby !== nothing) + && anchorThat && anchorThat.type === Anchor.MARK) : begin + set shiftx : [fallback anchorThis.mbx anchorThis.x] - anchorThat.x + set shifty : [fallback anchorThis.mby anchorThis.y] - anchorThat.y + # we have a mark-to-mark position + if (anchorThat.mbx !== nothing && anchorThat.mby !== nothing) : if (anchorThis.type === Anchor.BASE) + then : set this.anchors.(markid) : new Anchor + * (anchorThis.x + anchorThat.mbx - anchorThat.x) + * (anchorThis.y + anchorThat.mby - anchorThat.y) + * Anchor.BASE + else : set this.anchors.(markid) : new Anchor + * anchorThis.x + * anchorThis.y + * anchorThis.type + * (anchorThis.mbx + anchorThat.mbx - anchorThat.x) + * (anchorThis.mby + anchorThat.mby - anchorThat.y) + if contours : begin + local newcontours {} + foreach [contour : items-of contours] : begin + local c {} + set c.tag : contour.tag || component.tag || t.defaultTag + foreach [point : items-of contour] : begin + c.push : new Point (point.x + shiftx) (point.y + shifty) point.onCurve point.cubic point.subdivided + newcontours.push c + set this.contours : this.contours.concat newcontours + if (([not contours] || copyAnchors) && glyph.anchors) : begin + foreach [k : items-of : Object.keys glyph.anchors] : set this.anchors.(k) glyph.anchors.(k) + piecewise + glyph.name : this.dependencies.push glyph.name + glyph.dependencies : this.dependencies = [this.dependencies.concat glyph.dependencies] + return this + + public [apply-transform transform alsoAnchors] : begin + foreach [c : items-of this.contours] : foreach [j : range 0 c.length] : set c.(j) : tp transform c.(j) + if alsoAnchors : foreach key [items-of [Object.keys this.anchors]] : begin + set this.anchors.(key) : Anchor.transform transform this.anchors.(key) + return this + + public [set-anchor id type x y mbx mby] : begin + xytransform this.gizmo x y + if (mbx !== nothing && mby !== nothing) + : then : begin + xytransform this.gizmo mbx mby + set this.anchors.(id) : new Anchor x y type mbx mby + : else : set this.anchors.(id) : new Anchor x y type + return this + + public [cleanup t] : begin + foreach c [range 0 this.contours.length] : begin + local ocontour this.contours.(c) + # add infections + local contour {[new Point ocontour.0.x ocontour.0.y ocontour.0.onCurve]} + local flag 0 + foreach [j : range 1 (ocontour.length - 1)] : piecewise + flag : dec flag + ocontour.(j).onCurve : contour.push ocontour.(j) + ocontour.(j).cubic : begin + local p0 contour.(contour.length - 1) + local p1 ocontour.(j) + local p2 ocontour.(j + 1) + local p3 ocontour.(j + 2) + local strand : new Bezier p0.x p0.y p1.x p1.y p2.x p2.y p3.x p3.y + local ts [strand.inflections].y + piecewise + (!ts || ts.length < 1) : contour.push p1 p2 p3 + true : begin + set ts [ts.sort byx] + ts.unshift 0 + ts.push 1 + foreach [k : range 0 (ts.length - 1)] : begin + local s : strand.split ts.(k) ts.(k + 1) + if s.points : contour.push + new Point s.points.1.x s.points.1.y false true + new Point s.points.2.x s.points.2.y false true + new Point s.points.3.x s.points.3.y true + set flag 2 + true : begin + local p0 contour.(contour.length - 1) + local p1 ocontour.(j) + local p2 ocontour.(j + 1) + if [not p2.onCurve] : set p2 : new Point [mix p1.x p2.x 0.5] [mix p1.y p2.y 0.5] true + local strand : new Bezier p0.x p0.y p1.x p1.y p2.x p2.y + local ts [strand.inflections].y + piecewise + (!ts || ts.length < 1) : contour.push p1 p2 + true : begin + set ts [ts.sort byx] + ts.unshift 0 + ts.push 1 + foreach [k : range 0 (ts.length - 1)] : begin + local s : strand.split ts.(k) ts.(k + 1) + if s.points : contour.push + new Point s.points.1.x s.points.1.y false + new Point s.points.2.x s.points.2.y true + set flag 1 + + contour.push [new Point ocontour.(ocontour.length - 1).x ocontour.(ocontour.length - 1).y ocontour.(ocontour.length - 1).onCurve] - set cleanedContour {} - foreach j [range 0 contour.length] : if ([not contour.(j).cubic] && [not contour.(j).unimportant]) : begin - local found false - for [local k : j + 1] ((k < contour.length) && ( - [not contour.(k).cubic] && [closepoint contour.(j) contour.(k) t] - || contour.(k).cubic && contour.(k + 1).cubic - && [closepoint contour.(j) contour.(k) t] - && [closepoint contour.(j) contour.(k) t] - && [closepoint contour.(j) contour.(k + 2) t])) [inc k] : begin - set contour.(k).unimportant true - set found true - if (contour.(k).cubic && contour.(k + 1).cubic - && [closepoint contour.(j) contour.(k) t] - && [closepoint contour.(j) contour.(k) t] - && [closepoint contour.(j) contour.(k + 2) t]) : begin - set contour.(k + 1).unimportant true - set contour.(k + 2).unimportant true - set k : k + 2 - if found : begin - set contour.(j).onCurve true - set j (k - 1) - foreach point [items-of contour] : if [not point.unimportant] : cleanedContour.push point - this.contours.(c) = cleanedContour - return this \ No newline at end of file + # cleanup + 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 t] : set p1.unimportant true + foreach point [items-of contour] : if [not point.unimportant] : cleanedContour.push point + set contour cleanedContour + + set cleanedContour {} + foreach j [range 0 contour.length] : if ([not contour.(j).cubic] && [not contour.(j).unimportant]) : begin + local found false + for [local k : j + 1] ((k < contour.length) && ( + [not contour.(k).cubic] && [closepoint contour.(j) contour.(k) t] + || contour.(k).cubic && contour.(k + 1).cubic + && [closepoint contour.(j) contour.(k) t] + && [closepoint contour.(j) contour.(k) t] + && [closepoint contour.(j) contour.(k + 2) t])) [inc k] : begin + set contour.(k).unimportant true + set found true + if (contour.(k).cubic && contour.(k + 1).cubic + && [closepoint contour.(j) contour.(k) t] + && [closepoint contour.(j) contour.(k) t] + && [closepoint contour.(j) contour.(k + 2) t]) : begin + set contour.(k + 1).unimportant true + set contour.(k + 2).unimportant true + set k : k + 2 + if found : begin + set contour.(j).onCurve true + set j (k - 1) + foreach point [items-of contour] : if [not point.unimportant] : cleanedContour.push point + this.contours.(c) = cleanedContour + return this \ No newline at end of file diff --git a/support/spiroexpand.ptl b/support/spiroexpand.ptl index 742640b..14039ca 100644 --- a/support/spiroexpand.ptl +++ b/support/spiroexpand.ptl @@ -11,145 +11,138 @@ define-macro xytransform : syntax-rules set @y : @t * @tfm.xy + @y * @tfm.yy + @tfm.y ] -export all : define [SpiroExpansionContext] : begin - set this.gizmo [Transform.Id] - 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 - xytransform this.gizmo x 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) - 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 - -define [SpiroExpansionContext.prototype.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 - -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 [normalY angle] : Math.sin angle define [normalX angle vex] : [Math.cos angle] * vex -define [SpiroExpansionContext.prototype.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 +export all : class SpiroExpansionContext + public [new] : begin + set this.gizmo [Transform.Id] + set this.controlKnots {} + set this.defaultd1 0 + set this.defaultd2 0 - # 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} \ No newline at end of file + 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} \ No newline at end of file