diff --git a/buildglyphs.patel b/buildglyphs.patel index 6c0dacc81..d93575e16 100644 --- a/buildglyphs.patel +++ b/buildglyphs.patel @@ -9,6 +9,13 @@ define libspiro : require 'libspiro-js' define [mix a b p] : a + [b - a] * p define [linreg x0 y0 x1 y1 x] : y0 + [x - x0] * [y1 - y0] / [x1 - x0] +define [bilinear x0 x1 y0 y1 z00 z01 z10 z11 x y] : linreg { + * y0 + linreg x0 z00 x1 z10 x + * y1 + linreg x0 z01 x1 z11 x + * y +} define [fallback] : for [local j 0] [j < arguments.length] [inc j] : if [arguments`j !== nothing] : return arguments`j define emptyFontStr : JSON.stringify [require './empty.json'] @@ -79,10 +86,15 @@ define [buildFont para recursive] : begin { define globalTransform : Italify para.italicAngle define ITALICCOR : 1 / [Math.sqrt [1 - globalTransform.yx * globalTransform.yx]] + # Orient parameters define UPWARD (.x [-ITALICCOR] .y 0) define DOWNWARD (.x ITALICCOR .y 0) define RIGHTWARD (.x globalTransform.yx .y 1) define LEFTWARD (.x [- globalTransform.yx] .y [-1]) + define [normalize a] : begin { + local m : Math.hypot a.x a.y + return (.x [a.x / m] .y [a.y / m]) + } # derived metrics define XO : XH - O @@ -322,11 +334,11 @@ 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] : g4 [mix before.x after.x args.rx] [mix before.y after.y args.ry] unimportant + define [afInterpolate before after args] : g2 [mix before.x after.x args.rx] [mix before.y after.y args.ry] 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 : g4 [mix before.x after.x rx] [mix before.y after.y ry] unimportant + foreach (rx ry) [items-of args.rs] : knots.push : g2 [mix before.x after.x rx] [mix before.y after.y ry] unimportant return knots } @@ -398,13 +410,13 @@ define [buildFont para recursive] : begin { s.set-samples 1 local (.knots knots .closed closed .lastafs lastafs) : prepareSpiroKnots [().slice.call arguments 0] s libspiro.spiroToBezierOnContext knots closed s - foreach af : [items-of lastafs] : if af : af.call s + foreach af [items-of lastafs] : if af : af.call s return s } 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 - foreach af : [items-of lastafs] : if af : af.call this + foreach af [items-of lastafs] : if af : af.call this } ###### HERE WE GO! diff --git a/glyphs/common-shapes.patel b/glyphs/common-shapes.patel index 3d42b6d3d..4527cd101 100644 --- a/glyphs/common-shapes.patel +++ b/glyphs/common-shapes.patel @@ -101,8 +101,8 @@ define [xsStrand _xleft yleft _xright yright _halfstroke0 _halfstroke1 _ess _exp :.line-to _xleft [yleft - 1000] :.max-samples 1 :.to-outline].0.map : [p] -> [tp upright p] - local xItalicCorrection : -[outline.3.x - outline.0.x] / [2 * halfstroke0] - local yItalicCorrection : [outline.3.y - outline.0.y] / [2 * halfstroke0] + local xItalicCorrection : -[outline.5.x - outline.0.x] / [2 * halfstroke0] + local yItalicCorrection : [outline.5.y - outline.0.y] / [2 * halfstroke0] local roundsize : [_roundp || SMOOTHA * 0.4] * [if [yleft < yright] [-1] 1] local roundleft [yleft - roundsize] @@ -323,6 +323,55 @@ define [LegShape xt xb xs top bottom _fine] : glyph-construction { :.curve-to [linreg top xt [bottom + LONGJUT] xb [bottom + fine]] [bottom + fine] xs [bottom + fine] :.heads-to LEFTWARD } +# Spiro shapes +define [sband sw rtl _tension _tangle _compression] : return (.type 'interpolate' .af [lambda [before after] : begin { + local tension : fallback _tension 0.7 + local compression : fallback _compression 1 + local tensionw 0 + local minangle : -para.italicangle / 180 * Math.PI + local maxangle : Math.atan2 [after.x - before.x] [[mix after.y before.y tension] - [mix before.y after.y tension]] + set maxangle : Math.atan2 [[Math.sin maxangle] * compression] [Math.cos maxangle] + local p : fallback _tangle 0.25 + local pts () + local samples 32 + foreach j [range 1 samples] : begin { + local t : j / samples + local angle : mix minangle maxangle [bez3 0 p 1 1 : if [t < 1/2] [2 * t] [2 * [1 - t]]] + local k : bez3 0 tensionw [1 - tensionw] 1 t + if rtl : k = 1 - k + pts.push : g2 [mix before.x after.x [bez3 0 0 1 1 t]] [mix before.y after.y [bez3 0 tension [1 - tension] 1 t]] [widths.heading [[1 - k] * STROKE] [k * STROKE] (.x [Math.cos angle] .y [Math.sin angle])] + } + # throw 'w' + return pts +}]) +define [hookstart y p f] : return (.type 'interpolate' .af [lambda [before after] : begin { + local atBottom : after.y > y + local ltr : after.x > before.x + before.x = before.x - OXHOOK * [if ltr [-1] 1] + local hv : archv + local mixr : fallback p : linreg 1 0.5 [SMALLSMOOTH / HOOK] 0.53 [[after.y - y] / [before.y - y]] + local mx [[mix after.x before.x mixr] + [if atBottom 1 [-1]] * OMIDCOR_S] + return : list { + g4 mx y f + hv.af.call this (.x mx .y y) after hv + } +}]) +define [hookend y p f dontextend] : return (.type 'interpolate' .af [lambda [before after] : begin { + local atBottom : before.y > y + local ltr : after.x > before.x + after.x = after.x + OXHOOK * [if ltr [-1] 1] + local vh : arcvh + local mixr : fallback p : linreg 1 0.5 [SMALLSMOOTH / HOOK] 0.53 [[before.y - y] / [after.y - y]] + local mx [[mix before.x after.x mixr] + [if atBottom 1 [-1]] * OMIDCOR_S] + if [!dontextend && atBottom && ltr] : begin { + after.x = after.x + TAILADJX * globalTransform.yx + after.y = after.y - TAILADJY * globalTransform.yx + } + return : list { + vh.af.call this before (.x mx .y y) vh + g4 mx y f + } +}]) # Derived subfonts define [Miniature glyphs fold scale] : begin { @@ -370,32 +419,4 @@ define [vdual newid unicode id spacing] : create-glyph [fallback newid : 'double } apply-transform : Translate 0 [spacing / 2] apply-transform : Italify -} -define [hookstart y p f] (.type 'interpolate' .af [lambda [before after] : begin { - local atBottom : after.y > y - local ltr : after.x > before.x - before.x = before.x - OXHOOK * [if ltr [-1] 1] - local hv : archv - local mixr : fallback p : linreg 1 0.5 [SMALLSMOOTH / HOOK] 0.53 [[after.y - y] / [before.y - y]] - local mx [[mix after.x before.x mixr] + [if atBottom 1 [-1]] * OMIDCOR_S] - return : list { - g4 mx y f - hv.af.call this (.x mx .y y) after hv - } -}]) -define [hookend y p f dontextend] (.type 'interpolate' .af [lambda [before after] : begin { - local atBottom : before.y > y - local ltr : after.x > before.x - after.x = after.x + OXHOOK * [if ltr [-1] 1] - local vh : arcvh - local mixr : fallback p : linreg 1 0.5 [SMALLSMOOTH / HOOK] 0.53 [[before.y - y] / [after.y - y]] - local mx [[mix before.x after.x mixr] + [if atBottom 1 [-1]] * OMIDCOR_S] - if [!dontextend && atBottom && ltr] : begin { - after.x = after.x + TAILADJX * globalTransform.yx - after.y = after.y - TAILADJY * globalTransform.yx - } - return : list { - vh.af.call this before (.x mx .y y) vh - g4 mx y f - } -}]) \ No newline at end of file +} \ No newline at end of file diff --git a/glyphs/latin-basic-capital.patel b/glyphs/latin-basic-capital.patel index 8d4f900de..0026d9597 100644 --- a/glyphs/latin-basic-capital.patel +++ b/glyphs/latin-basic-capital.patel @@ -594,8 +594,17 @@ create-glyph 'S' : glyph-construction { set-width WIDTH assign-unicode 'S' include capitalMarks - - include : sHookUpper CAP SMOOTHA HOOK - include : sHookLower 0 SMOOTHA HOOK - include : sStrand [CAP - SMOOTHA] SMOOTHA + include : spiro { + widths.lhs + g4 RIGHTSB [CAP - HOOK] + hookstart CAPO + g4 SB [CAP - SMOOTHA] + sband + g4 RIGHTSB SMOOTHA [widths 0 STROKE] + hookend O + g4 SB HOOK + } +# include : sHookUpper CAP SMOOTHA HOOK +# include : sHookLower 0 SMOOTHA HOOK +# include : sStrand [CAP - SMOOTHA] SMOOTHA } \ No newline at end of file diff --git a/glyphs/latin-basic-lower.patel b/glyphs/latin-basic-lower.patel index 8cff4961b..a7bdae78e 100644 --- a/glyphs/latin-basic-lower.patel +++ b/glyphs/latin-basic-lower.patel @@ -127,9 +127,9 @@ define [SmallEShape top stroke barpos] : glyph-construction { flat [RIGHTSB - O] barbottom [heading UPWARD] curl [RIGHTSB - O] [top - SMALLSMOOTHB] arcvh - g4 [MIDDLE - OMIDCOR_S] [XH - O] + g4 [MIDDLE - OMIDCOR_S] [top - O] archv - flat [SB + O] [XH - SMALLSMOOTHA] + flat [SB + O] [top - SMALLSMOOTHA] curl [SB + O] [0 + SMALLSMOOTHB] hookend O g4 [RIGHTSB - O] HOOK @@ -623,10 +623,16 @@ create-glyph 's' : glyph-construction { assign-unicode 's' include eMarks - include : sHookUpper XH [SMOOTHA * 0.87] SHOOK - include : sHookLower 0 [SMOOTHA * 0.87] SHOOK - - include : sStrand [XH - [SMOOTHA * 0.87]] [SMOOTHA * 0.87] 0.2 0.45 + include : spiro { + widths.lhs + 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] + g4 RIGHTSB [SMOOTHA * 0.85] [widths 0 STROKE] + hookend O SBALANCE + g4 SB SHOOK + } } ### r diff --git a/glyphs/symbol-ascii.patel b/glyphs/symbol-ascii.patel index e6dfb25c7..ddc1a6287 100644 --- a/glyphs/symbol-ascii.patel +++ b/glyphs/symbol-ascii.patel @@ -236,9 +236,15 @@ create-glyph 'semicolon' : glyph-construction { create-glyph 'question' : glyph-construction { set-width WIDTH assign-unicode '?' - include : xsStrand [MIDDLE - HALFSTROKE * ITALICCOR] [mix [DOTSIZE + STROKE] [XH / 2] 0.5] RIGHTSB [CAP - SMOOTHB] - include : twoHookUpper CAP SMOOTHB HOOK include : list : Ring [DOTSIZE - O] O [MIDDLE - DOTRADIUS + O] [MIDDLE + DOTRADIUS - O] true + include : spiro { + widths.rhs + g4 SB [CAP - HOOK] + hookstart CAPO + g4 RIGHTSB [CAP - SMOOTHB * 0.85] + flat [MIDDLE + HALFSTROKE * ITALICCOR] [mix [DOTSIZE + STROKE] [XH / 2] 0.5] + curl [MIDDLE + HALFSTROKE * ITALICCOR] [[mix [DOTSIZE + STROKE] [XH / 2] 0.5] - 1] + } } create-glyph 'exclam' : glyph-construction { set-width WIDTH diff --git a/pass2-finalize.py b/pass2-finalize.py index c39e5b933..37a73a623 100644 --- a/pass2-finalize.py +++ b/pass2-finalize.py @@ -6,6 +6,7 @@ font = fontforge.open(source) font.selection.all() font.removeOverlap() +font.addExtrema() font.simplify(1) font.canonicalContours() font.canonicalStart() diff --git a/support/stroke.patel b/support/stroke.patel index 22f21c2da..373cd6818 100644 --- a/support/stroke.patel +++ b/support/stroke.patel @@ -5,7 +5,7 @@ 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 [mix a b p] : a + [b - a] * p define [xs-array a] ([a.0 - 1] :: [a.concat ([a`[a.length - 1] + 1])]) define [ys-array a] (a.0 :: [a.concat (a`[a.length - 1])]) @@ -161,6 +161,7 @@ define [Stroke.prototype.to-outline d1 d2 _samples straight] : begin { local p0 this.points.0 local arcLengthSofar 0 + local unjoinedSeglength 0 for [local j 1] [j < this.points.length] [inc j] : begin { local p1 this.points`j piecewise { @@ -242,9 +243,11 @@ define [Stroke.prototype.to-outline d1 d2 _samples straight] : begin { for [local j 0] [j < subSegments.length] [inc j] : begin { local curve subSegments`j local segLength : curve.length + set unjoinedSeglength : unjoinedSeglength + segLength local segLengths (0) local samples : Math.min maxSamples : Math.max minSamples : Math.ceil : segLength / 100 + if [segLength <= 5] : samples = 1 foreach sample [range 1 till samples] : begin { segLengths.push : curve.split 0 [sample / samples] :.length @@ -281,21 +284,22 @@ define [Stroke.prototype.to-outline d1 d2 _samples straight] : begin { if [[not straight] && [nonlinear lthis lnext dlthis] && [nonlinear lthis lnext dlnext] && [il.x != null] && [il.y != null] && [nonlinear lthis il lnext] && [near lthis il lnext]] [then { left.push (.x il.x .y il.y .onCurve false) (.x lnext.x .y lnext.y .onCurve true) }] [else { - left.push (.x lnext.x .y lnext.y .onCurve true) + left.push (.x [mix lthis.x lnext.x 0.5] .y [mix lthis.y lnext.y 0.5]) (.x lnext.x .y lnext.y .onCurve true) }] local ir : intersection rthis.x rthis.y drthis.x drthis.y rnext.x rnext.y drnext.x drnext.y if [[not straight] && [nonlinear rthis rnext drthis] && [nonlinear rthis rnext drnext] && [ir.x != null] && [ir.y != null] && [nonlinear rthis ir rnext] && [near rthis ir rnext]] [then { right.push (.x ir.x .y ir.y .onCurve false) (.x rnext.x .y rnext.y .onCurve true) }] [else { - right.push (.x rnext.x .y rnext.y .onCurve true) + right.push (.x [mix rthis.x rnext.x 0.5] .y [mix rthis.y rnext.y 0.5]) (.x rnext.x .y rnext.y .onCurve true) }] } arcLengthSofar = arcLengthSofar + segLength - if brk.(j) : begin { + if [brk.(j) && unjoinedSeglength >= 100] : begin { shapes.push : left.concat [right.reverse] set left () set right () + set unjoinedSeglength 0 } } if [left.length + right.length > 2] : begin {