diff --git a/glyphs/common-shapes.ptl b/glyphs/common-shapes.ptl index 46e4ad1..04f56a9 100644 --- a/glyphs/common-shapes.ptl +++ b/glyphs/common-shapes.ptl @@ -287,6 +287,7 @@ define [halfXStrand _leftx lefty rightx righty turn straight tension _fine] : gl quadcontrols 0 ((cyleft - turnyleft) / (straightyleft - turnyleft)) 24 flat straightxleft straightyleft curl rightx righty + end [function : set this.cleanmore true] define [xStrand _leftx lefty _rightx righty turn straight tension] : glyph-construction diff --git a/glyphs/letters-unified-basic.ptl b/glyphs/letters-unified-basic.ptl index 5f62be6..9dbe34f 100644 --- a/glyphs/letters-unified-basic.ptl +++ b/glyphs/letters-unified-basic.ptl @@ -433,20 +433,21 @@ define VShape : symbol-block 'V, v and nu' widths.rhs straight.left.start (RIGHTSB + HOOKX / 3) (top - STROKE) g4 (RIGHTSB - HOOKX / 3) (top - HALFSTROKE - HOOK) - quadcontrols 0.4 0.75 10 unimportant g2 + quadcontrols 0.4 0.75 64 unimportant g4 (MIDDLE + cornerdist) 0 [widths.rhs VShape.fine] + #end [function : set this.dense true] create-glyph 'vhooktop' : glyph-construction assign-unicode 0x2C71 include eMarks include : VHooktopShape XH - + create-glyph 'cyrIzhitsa' : glyph-construction assign-unicode 0x474 include capitalMarks include : VHooktopShape CAP alias 'cyrizhitsa' 0x475 'vhooktop' - + turned 'turnv' 0x28C 'v' MIDDLE (XH / 2) create-glyph 'nu' : glyph-construction @@ -655,7 +656,7 @@ symbol-block 'X' include : AIHSerifs CAP alias 'Chi' 0x3A7 'X' alias 'cyrHa' 0x425 'X' - + ###console.log 'x' create-glyph 'x' : glyph-construction include eMarks assign-unicode 'x' @@ -664,7 +665,7 @@ symbol-block 'X' include : xStrand SB 0 RIGHTSB XH 0.02 0.4 0.14 include : xStrand SB XH RIGHTSB 0 0.02 0.4 0.14 include : AIHSerifs XH - + #if [not recursive] : throw 'xxx' create-glyph 'chi' : glyph-construction set-width WIDTH assign-unicode 0x3C7 @@ -707,8 +708,9 @@ symbol-block 'Y' widths.rhs straight.left.start (RIGHTSB + HOOKX / 3) (CAP - STROKE) g4 (RIGHTSB - HOOKX / 3) (CAP - HOOK) - quadcontrols 0.55 0.7 8 unimportant - g4 (MIDDLE + STROKE / 2 * HVCONTRAST) cross + quadcontrols 0.55 0.7 32 unimportant + g4 (MIDDLE + STROKE / 2 * HVCONTRAST) cross + end [function : set this.loose true] create-glyph 'cyrue' : glyph-construction assign-unicode 0x4AF @@ -723,13 +725,16 @@ symbol-block 'Y' ### y symbol-block 'y' local px1 0.84 - local py1 0.76 + local py1 : linreg 18 0.8 126 0.76 STROKE local px2 0.95 - local py2 : if SLAB [linreg 18 0.97 126 0.85 STROKE] 0.88 + local py20 0.88 + local py2 : if SLAB [linreg 18 0.97 126 0.85 STROKE] py20 local pds 0.05 local pds2 0.01 local dpy1 : (1 - [linreg (1 - px2) (1 - py2) px1 py1 1]) / (1 - py1) + local dpy20 : (1 - [linreg (1 - px1) (1 - py1) px2 py20 1]) / (1 - py20) local dpy2 : (1 - [linreg (1 - px1) (1 - py1) px2 py2 1]) / (1 - py2) + local yrstrokel0 : MIDDLE - WIDTH * 0.1 local yrstrokel : MIDDLE - WIDTH * [if SLAB 0.15 0.1] + [if SLAB HALFSTROKE 0] local yrstroker : RIGHTSB - HALFSTROKE * HVCONTRAST local yshrink : linreg 18 1 126 0.85 STROKE @@ -758,13 +763,15 @@ symbol-block 'y' curl yrstroker (top - ds) [heading DOWNWARD] quadcontrols 0 dpy1 16 yBaseKnots top bottom + end [function : set this.cleanmore true] include : dispiro widths.center flat (WIDTH - yrstroker) top [heading DOWNWARD] curl (WIDTH - yrstroker) (top - ds) [heading DOWNWARD] quadcontrols 0 dpy1 16 - flat [mix (WIDTH - yrstrokel) (WIDTH - yrstroker) px1] [mix (bottom + ds) (top - ds) py1] + flat [mix (WIDTH - yrstrokel) (WIDTH - yrstroker) px1] [mix (bottom + ds2) (top - ds) py1] curl MIDDLE [mix (bottom + ds2) (top - ds) [linreg (1 - px2) (1 - py2) px1 py1 ((MIDDLE - yrstrokel) / (yrstroker - yrstrokel))]] [widths.center (STROKE * yshrink)] + end [function : set this.cleanmore true] if SLAB : begin include : AIVSerifs top create-glyph 'y.upright' : glyph-construction @@ -803,6 +810,7 @@ symbol-block 'y' widths.center straight.left.start ([mix yrstrokel yrstroker px1] + HOOKX) (XH - HALFSTROKE) yBaseKnots XH DESCENDER + end [function : set this.cleanmore true] create-glyph 'cyrU' : glyph-construction assign-unicode 0x423 @@ -812,14 +820,30 @@ symbol-block 'y' create-glyph 'lambda' : glyph-construction assign-unicode 0x3BB include bMarks + local top CAP + local bottom 0 + local ds : (top - bottom) * pds + local ds2 : (top - bottom) * pds2 + include : tagged 'strokeR' : dispiro + widths.center + flat yrstroker bottom [heading UPWARD] + curl yrstroker (bottom + ds) [heading UPWARD] + quadcontrols 0 dpy1 16 + flat [mix yrstroker yrstrokel0 (1 - px1)] [mix (top - ds2) (bottom + ds) py1] + curl [mix yrstroker yrstrokel0 px2] [mix (top - ds2) (bottom + ds) (1 - py20)] + quadcontrols 1 (1 - dpy20) 16 + flat yrstrokel0 (top - ds2) [heading UPWARD] + curl yrstrokel0 top [heading UPWARD] + end [function : set this.cleanmore true] - local xTop : mix SB RIGHTSB 0.28 - local turnp : XH / (XH - DESCENDER) - local xb : mix SB RIGHTSB 0.51 - local yb : mix XH 0 (0.05 * turnp) - - include : xStrand xTop CAP RIGHTSB 0 0.1 0.6 0.14 - include : halfXStrand SB 0 xb yb (0.1 * turnp) 0.4 (0.14 * turnp) + include : dispiro + widths.center + flat (WIDTH - yrstroker) bottom [heading UPWARD] + curl (WIDTH - yrstroker) (bottom + ds) [heading UPWARD] + quadcontrols 0 dpy1 16 + flat [mix (WIDTH - yrstrokel0) (WIDTH - yrstroker) px1] [mix (top - ds2) (bottom + ds) py1] + curl MIDDLE [mix (top - ds2) (bottom + ds) [linreg (1 - px2) (1 - py20) px1 py1 ((MIDDLE - yrstrokel0) / (yrstroker - yrstrokel0))]] + end [function : set this.cleanmore true] create-glyph 'lambdaslash' : glyph-construction assign-unicode 0x19B @@ -1550,7 +1574,6 @@ define {CShape} : symbol-block 'C' arcvh flat MIDDLE (XH - HALFSTROKE - O) [widths HALFSTROKE 0] curl (MIDDLE - 1) (XH - HALFSTROKE - O) - create-glyph 'sigmafinal' : glyph-construction assign-unicode 0x3C2 include pMarks @@ -1696,7 +1719,6 @@ symbol-block 'O' assign-unicode 'o' include eMarks include : OShape XH 0 SB RIGHTSB nothing nothing nothing true - alias 'omicron' 0x3BF 'o' alias 'cyro' 0x43e 'o' diff --git a/glyphs/letters-unified-extended.ptl b/glyphs/letters-unified-extended.ptl index c28a30d..523c604 100644 --- a/glyphs/letters-unified-extended.ptl +++ b/glyphs/letters-unified-extended.ptl @@ -194,6 +194,7 @@ symbol-block 'Delta and cyrbe' curl (SB + OX) (XH - SMALLSMOOTHA) alsothruthem [list {0.25 0.79} {0.5 0.87}] important g4 (RIGHTSB - HALFSTROKE * HVCONTRAST) CAP + #end : function : set this.dense true symbol-block 'epsilon and cyrze' define [SmallEpsilonShape top hook] : glyph-construction diff --git a/glyphs/overmarks.ptl b/glyphs/overmarks.ptl index 586c200..66fb6dd 100644 --- a/glyphs/overmarks.ptl +++ b/glyphs/overmarks.ptl @@ -108,8 +108,8 @@ symbol-block 'Above marks' local top : ttop + (markFine * 2) local bot : tbot - (markFine * 2) - local tildeWave [linreg 16 2.5 90 1.36 (markHalfStroke * 2)] - local tildeWaveX 0.52 + local tildeWave [linreg 16 2.6 90 1.36 (markHalfStroke * 2)] + local tildeWaveX [linreg 16 0.5 90 0.52 (markHalfStroke * 2)] local tildeWaveEnd : [smoothreg {16 60 90} {(-0.025) (-0.05) 0}] (markHalfStroke * 2) include : dispiro @@ -118,9 +118,9 @@ symbol-block 'Above marks' bezcontrols.absolute * [mix leftEnd rightEnd tildeWaveX]; * [mix bot top tildeWave] * [mix leftEnd rightEnd (1 - tildeWaveX)]; * [mix bot top (1 - tildeWave)] - * 128; * important; * g2 + * 256; * important; * g2 g2 rightEnd [mix tbot ttop (1 - tildeWaveEnd)] - end [function : set this.dense true] + #end [function : set this.dense true] set this.dense true diff --git a/support/fairify.js b/support/fairify.js index 4652811..190fcdb 100644 --- a/support/fairify.js +++ b/support/fairify.js @@ -2,10 +2,12 @@ var Transform = require('./transform.js'); var ANGLES = 12; var VERYCROWD = 2; -var SMALLANGLE = 0.1; +var SMALLANGLE = 0.05; +var SMALLANGLE_CLEANMORE = 0.075; var CROWD = 4; var LOOSE = 6; var SMALL = 1e-4 +var CLOSE = 1; function cross(z1, z2, z3){ return (z2.x - z1.x) * (z3.y - z1.y) - (z3.x - z1.x) * (z2.y - z1.y) }; @@ -161,24 +163,32 @@ function fitpts(p1, c1, c2, p2){ } return [mix(mid, p1, 1/3), mix(mid, p2, 1/3)] }; +function distance(z1, z2){ + return Math.sqrt((z1.x - z2.x) * (z1.x - z2.x) + (z1.y - z2.y) * (z1.y - z2.y)) +} function veryclose(z1, z2){ return (z1.x - z2.x) * (z1.x - z2.x) + (z1.y - z2.y) * (z1.y - z2.y) <= SMALL }; function angleBetween(z1, z2, z3, z4){ - return Math.atan2(z2.y - z1.y, z2.x - z1.x) - Math.atan2(z4.y - z3.y, z4.x - z3.x); + return (Math.atan2(z2.y - z1.y, z2.x - z1.x) - Math.atan2(z4.y - z3.y, z4.x - z3.x)) % Math.PI; }; +function pldistance(z1, z2, z){ + return Math.abs((z2.y - z1.y) * z.x - (z2.x - z1.x) * z.y + z2.x * z1.y - z2.y * z1.x) / Math.sqrt((z2.x - z1.x) * (z2.x - z1.x) + (z2.y - z1.y) * (z2.y - z1.y)) +} function estimateSegments(z1, z2){ var hspan = Math.abs(z1.x - z2.x); var vspan = Math.abs(z1.y - z2.y); return hspan <= 5 * CROWD || vspan <= 5 * CROWD ? VERYCROWD : hspan <= 10 * LOOSE || vspan <= 10 * LOOSE ? CROWD : LOOSE; }; -function enoughRotate(bef, z0, z1, z2, aft){ - var angleRotatedBefore = Math.abs(angleBetween(z1, bef, z1, z0)); - var angleRotatedAfter = Math.abs(angleBetween(z1, aft, z1, z2)); - return !((angleRotatedBefore < SMALLANGLE || angleRotatedBefore > Math.PI - SMALLANGLE) - || (angleRotatedAfter < SMALLANGLE || angleRotatedAfter > Math.PI - SMALLANGLE)) -} -function fairify(scurve, gizmo, denseQ){ +function enoughRotate(bef, z0, z1, z2, aft, cleanMore, flagl, flagr){ + var angleRotatedBefore = Math.abs(angleBetween(bef.next || z1, bef, z1, z0)); + var angleRotatedAfter = Math.abs(angleBetween(aft.prev || z1, aft, z1, z2)); + var smallanglel = (cleanMore ? SMALLANGLE_CLEANMORE : SMALLANGLE) * (flagl && cleanMore ? 1 : 2); + var smallangler = (cleanMore ? SMALLANGLE_CLEANMORE : SMALLANGLE) * (flagr && cleanMore ? 1 : 2); + return !((angleRotatedBefore < smallanglel || angleRotatedBefore > Math.PI - smallanglel) && pldistance(z1, z0, bef) <= CLOSE && distance(z1, bef) <= 5 * CROWD + || (flagr ? false : (angleRotatedAfter < smallangler || angleRotatedAfter > Math.PI - smallangler) && pldistance(z1, z2, aft) <= CLOSE && distance(z1, aft) <= 5 * CROWD)) +}; +function fairify(scurve, gizmo, denseQ, cleanMore){ for(var j = 0; j < scurve.length; j++){ scurve[j] = Transform.untransform(gizmo, scurve[j]) } @@ -207,6 +217,12 @@ function fairify(scurve, gizmo, denseQ){ }; // Mark corners and extremae for(var j = 1; j < splitpoints.length - 1; j++) { + if(splitpoints[j].onCurve && !splitpoints[j - 1].onCurve){ + splitpoints[j].prev = splitpoints[j - 1] + } + if(splitpoints[j].onCurve && !splitpoints[j + 1].onCurve){ + splitpoints[j].next = splitpoints[j + 1] + } if(splitpoints[j].onCurve && !splitpoints[j - 1].onCurve && !splitpoints[j + 1].onCurve){ var z1 = splitpoints[j], z0 = splitpoints[j - 1], z2 = splitpoints[j + 1] if(cross(z1, z0, z2) < 1e-6 && dot(z1, z0, z2) < 0){ @@ -214,6 +230,20 @@ function fairify(scurve, gizmo, denseQ){ var angle = Math.abs(angle0 / Math.PI * 2 % 1); if(Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL || angle <= SMALL || angle >= 1 - SMALL){ z1.mark = true; // extrema + z1.inflect = false; + } else { + var isInflection = false; + if(j > 2 && j < splitpoints.length - 2){ + var za = bez3(z1, z0, splitpoints[j - 2], splitpoints[j - 3], SMALL); + var zb = bez3(z1, z2, splitpoints[j + 2], splitpoints[j + 3], SMALL); + var inflect = ((z0.y-z2.y)*(za.x-z0.x) + (z2.x-z0.x)*(za.y-z0.y)) + * ((z0.y-z2.y)*(zb.x-z0.x) + (z2.x-z0.x)*(zb.y-z0.y)); + if(inflect < 0) isInflection = true; + }; + if(z1.inflect || isInflection) { + z1.mark = true; + z1.asinflect = true; + } } } else { z1.mark = true; // also corner @@ -223,31 +253,24 @@ function fairify(scurve, gizmo, denseQ){ } }; splitpoints[0].mark = splitpoints[splitpoints.length - 1].mark = true; - // Mark infections - var lastmark = splitpoints[0]; - for(var k = 1; k < splitpoints.length && !splitpoints[k].mark; k++); - var nextmark = splitpoints[k]; - for(var j = 1; j < splitpoints.length - 1; j++) { - if(splitpoints[j].mark){ - lastmark = splitpoints[j]; - for(var k = j + 1; k < splitpoints.length && !splitpoints[k].mark; k++); - nextmark = splitpoints[k]; - } - if(splitpoints[j].onCurve && !splitpoints[j - 1].onCurve && !splitpoints[j + 1].onCurve){ - var z1 = splitpoints[j], z0 = splitpoints[j - 1], z2 = splitpoints[j + 1] - if(cross(z1, z0, z2) < 1e-6 && dot(z1, z0, z2) < 0){ - var isInflection = false; - if(j > 2 && j < splitpoints.length - 2){ - var za = bez3(z1, z0, splitpoints[j - 2], splitpoints[j - 3], SMALL); - var zb = bez3(z1, z2, splitpoints[j + 2], splitpoints[j + 3], SMALL); - var inflect = ((z0.y-z2.y)*(za.x-z0.x) + (z2.x-z0.x)*(za.y-z0.y)) * ((z0.y-z2.y)*(zb.x-z0.x) + (z2.x-z0.x)*(zb.y-z0.y)); - if(inflect < 0) isInflection = true; - }; - if((z1.inflect || isInflection) && (denseQ || enoughRotate(lastmark, z0, z1, z2, nextmark))) { - z1.mark = true; + // Mark cleanup inflections + for(var pass = 0; pass < 2; pass++){ + for(var j = 1; j < splitpoints.length - 1; j++) { + if(splitpoints[j].mark){ + for(var k = j - 1; k >= 0 && !splitpoints[k].mark; k--); + lastmark = splitpoints[k]; + for(var k = j + 1; k < splitpoints.length && !splitpoints[k].mark; k++); + nextmark = splitpoints[k]; + } + if(splitpoints[j].mark && splitpoints[j].asinflect){ + var z1 = splitpoints[j], z0 = splitpoints[j - 1], z2 = splitpoints[j + 1] + if(!(denseQ || enoughRotate(lastmark, z0, z1, z2, nextmark, cleanMore, lastmark.asinflect, nextmark.asinflect))) { + //z1.mark = false; + z0.remove = z1.remove = z2.remove = true; } } - } + }; + for(var j = 0; j < splitpoints.length; j++) if(splitpoints[j].remove) splitpoints[j].mark = false; }; // Mark diagonals var lastmark = splitpoints[0]; @@ -267,8 +290,8 @@ function fairify(scurve, gizmo, denseQ){ var angle = Math.abs(angle0 / Math.PI * segments % 1); var angleRotatedBefore = Math.abs(angleBetween(z1, lastmark, z1, z0)); var angleRotatedAfter = Math.abs(angleBetween(z1, nextmark, z1, z2)); - if(!(denseQ || enoughRotate(lastmark, z0, z1, z2, nextmark)) - || !(Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL || angle <= SMALL || angle >= 1 - SMALL)){ + if(!(Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL || angle <= SMALL || angle >= 1 - SMALL) + || !(denseQ || enoughRotate(lastmark, z0, z1, z2, nextmark))){ z1.remove = z0.remove = z2.remove = true; } } diff --git a/support/spirokit.ptl b/support/spirokit.ptl index a3194c0..c1f69ba 100644 --- a/support/spirokit.ptl +++ b/support/spirokit.ptl @@ -165,7 +165,7 @@ export : define [SetupBuilders args] : begin lhs.0.type = rhs.0.type = lhs.(lhs.length - 1).type = rhs.(rhs.length - 1).type = 'corner' libspiro.spiroToBezierOnContext [lhs.concat : rhs.reverse] true g QUAD PRECISION if ([not s.unfair] && [not para.unfair]) : foreach [j : range 0 g.contours.length] : begin - set g.contours.(j) : fairify g.contours.(j) globalTransform s.dense + set g.contours.(j) : fairify g.contours.(j) globalTransform s.dense s.cleanmore set g.knots knots set g.lhsknots lhs set g.rhsknots rhs @@ -177,7 +177,7 @@ export : define [SetupBuilders args] : begin libspiro.spiroToBezierOnContext knots closed g QUAD PRECISION foreach af [items-of lastafs] : if af : af.call g if [not para.unfair] : foreach [j : range 0 g.contours.length] : begin - set g.contours.(j) : fairify g.contours.(j) (g.fairGizmo || g.gizmo) g.dense + set g.contours.(j) : fairify g.contours.(j) (g.fairGizmo || g.gizmo) g.dense g.cleanmore this.include g return [object