extern isFinite import 'cubic2quad' as cubic2quad import 'bezier-js' as Bezier import './point' as Point import './transform' as : Transform && [object [transformPoint tp] [untransform utp] inverse] import './anchor' as Anchor 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 [mix a b p] : a + (b - a) * p 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) 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 local xm : (a.x + c.x) / 2 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 PRECISION 1000 define [cov x] : piecewise [isFinite x] : return : [Math.round : x * PRECISION] / PRECISION true : return 0 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 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 : public 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 : public 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 : public 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 : public 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 : public 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 [depends-on glyph] : begin piecewise glyph.name : this.dependencies.push glyph.name glyph.dependencies : this.dependencies = [this.dependencies.concat glyph.dependencies] return this public [include component copyAnchors copyWidth] : 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) if (glyph.advanceWidth >= 0 && copyWidth) : set this.advanceWidth glyph.advanceWidth 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 static [contourToSVGPath contour delta] : if (contour && contour.length) : begin local lx contour.0.x local ly contour.0.y local buf : if delta "" "M \[cov lx] \[cov ly]" for [local j 1] (j < contour.length) [inc j] : begin local point contour.(j) piecewise point.onCurve : begin if delta : then : set buf : buf + "l \[cov : point.x - lx] \[cov : point.y - ly]" : else : set buf : buf + "L \[cov point.x] \[cov point.y]" set {.x lx .y ly} point point.cubic : begin local z1 point local z2 contour.(j + 1) local z3 contour.(j + 2) if delta : then : set buf : buf + "c \[cov : z1.x - lx] \[cov : z1.y - ly] \[cov : z2.x - lx] \[cov : z2.y - ly] \[cov : z3.x - lx] \[cov : z3.y - ly]" : else : set buf : buf + "C \[cov z1.x] \[cov z1.y] \[cov z2.x] \[cov z2.y] \[cov z3.x] \[cov z3.y]" set {.x lx .y ly} z3 set j : j + 2 true : begin local zc point local zf : if contour.(j + 1) contour.(j + 1) contour.0 local x1 : mix lx zc.x (2 / 3) local y1 : mix ly zc.y (2 / 3) local x2 : mix zf.x zc.x (2 / 3) local y2 : mix zf.y zc.y (2 / 3) if delta : then : set buf : buf + "c \[cov : x1 - lx] \[cov : y1 - ly] \[cov : x2 - lx] \[cov : y2 - ly] \[cov : zf.x - lx] \[cov : zf.y - ly]" : else : set buf : buf + "C \[cov x1] \[cov y1] \[cov x2] \[cov y2] \[cov zf.x] \[cov zf.y]" set {.x lx .y ly} zf inc j set buf : buf + " Z\n" return buf 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.extrema].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 : begin contour.push : new Point s.points.1.x s.points.1.y false true contour.push : new Point s.points.2.x s.points.2.y false true contour.push : 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.extrema].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] # 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