Further improve normal corrector in spiro expansion
This commit is contained in:
parent
bb863cd4ff
commit
040b5fd487
7 changed files with 58 additions and 43 deletions
|
@ -10,7 +10,6 @@ glyph-block Letter-Latin-Eszet : begin
|
||||||
glyph-block-import CommonShapes
|
glyph-block-import CommonShapes
|
||||||
glyph-block-import Common-Derivatives
|
glyph-block-import Common-Derivatives
|
||||||
glyph-block-import Letter-Shared-Shapes : FlatHookDepth
|
glyph-block-import Letter-Shared-Shapes : FlatHookDepth
|
||||||
glyph-block-import Letter-Latin-S : SNeck
|
|
||||||
|
|
||||||
local ymiddle : [mix 0 CAP 0.5] - HalfStroke
|
local ymiddle : [mix 0 CAP 0.5] - HalfStroke
|
||||||
local ymiddleCap : [mix 0 CAP 0.54] - HalfStroke
|
local ymiddleCap : [mix 0 CAP 0.54] - HalfStroke
|
||||||
|
|
|
@ -23,13 +23,11 @@ glyph-block Letter-Latin-S : begin
|
||||||
define SLAB-INWARD 2
|
define SLAB-INWARD 2
|
||||||
|
|
||||||
define SOBot OX
|
define SOBot OX
|
||||||
define [SmallSTensionCoeff stroke] : mix [StrokeWidthBlend 1 0.375 stroke] 1 : clamp 0 1 : Math.pow (Width / (UPM * 0.55)) 0.5
|
|
||||||
define [SmallSDistCoeff] : Math.min 0.25 (0.1 * [Math.max 1 (Width / HalfUPM)])
|
|
||||||
|
|
||||||
glyph-block-export SNeck
|
define [SNeck] : params [[stroke Stroke]] : begin
|
||||||
define [SNeck] : params [[stroke Stroke] [slantCoeff 0] [tensionCoeff 1] [dist 0.1]] : begin
|
define tensionCoeff : mix [StrokeWidthBlend 1 0.375 stroke] 1 : clamp 0 1 : Math.pow (Width / (UPM * 0.55)) 0.5
|
||||||
define strokeCoeff : StrokeWidthBlend 0 1 stroke
|
define dist : Math.min 0.25 (0.1 * [Math.max 1 (Width / HalfUPM)])
|
||||||
define tension : tensionCoeff * (0.5 - 0.4 * dist - 0.005 * strokeCoeff + slantCoeff * TanSlope)
|
define tension : tensionCoeff * (0.5 - 0.4 * dist)
|
||||||
return : alsoThruThem {{(0.5 - dist) tension} {(0.5 + dist) (1 - tension)}} [widths.center stroke]
|
return : alsoThruThem {{(0.5 - dist) tension} {(0.5 + dist) (1 - tension)}} [widths.center stroke]
|
||||||
|
|
||||||
define [SStrokeDollarInterruptGap deltaX top bot gap stroke refSwEss] : begin
|
define [SStrokeDollarInterruptGap deltaX top bot gap stroke refSwEss] : begin
|
||||||
|
@ -112,7 +110,7 @@ glyph-block Letter-Latin-S : begin
|
||||||
hookstart (XH - O) (sw -- stroke) (swItalicAdj -- Stroke)
|
hookstart (XH - O) (sw -- stroke) (swItalicAdj -- Stroke)
|
||||||
|
|
||||||
g4 (SB - OX) (XH - smooth)
|
g4 (SB - OX) (XH - smooth)
|
||||||
SNeck (stroke -- ess) (slantCoeff -- -0.02) (dist -- [SmallSDistCoeff]) (tensionCoeff -- [SmallSTensionCoeff stroke])
|
SNeck (stroke -- ess)
|
||||||
g4 (RightSB + OX - SOBot) (smooth) [widths 0 stroke]
|
g4 (RightSB + OX - SOBot) (smooth) [widths 0 stroke]
|
||||||
|
|
||||||
match sb
|
match sb
|
||||||
|
@ -155,7 +153,7 @@ glyph-block Letter-Latin-S : begin
|
||||||
g4 (RightSB + OX) (XH - SHook)
|
g4 (RightSB + OX) (XH - SHook)
|
||||||
hookstart (XH - O) (sw -- stroke) (swItalicAdj -- Stroke)
|
hookstart (XH - O) (sw -- stroke) (swItalicAdj -- Stroke)
|
||||||
g4 (SB - OX) (XH - smooth)
|
g4 (SB - OX) (XH - smooth)
|
||||||
SNeck (stroke -- ess) (slantCoeff -- -0.02) (dist -- [SmallSDistCoeff]) (tensionCoeff -- [SmallSTensionCoeff stroke])
|
SNeck (stroke -- ess)
|
||||||
g4 (RightSB + OX - SOBot) (smooth) [widths 0 stroke]
|
g4 (RightSB + OX - SOBot) (smooth) [widths 0 stroke]
|
||||||
arcvh
|
arcvh
|
||||||
flat (Middle - CorrectionOMidS) 0 [widths 0 Stroke]
|
flat (Middle - CorrectionOMidS) 0 [widths 0 Stroke]
|
||||||
|
@ -175,7 +173,7 @@ glyph-block Letter-Latin-S : begin
|
||||||
g4 (SB - OX) (XH - SHook) [widths.rhs stroke]
|
g4 (SB - OX) (XH - SHook) [widths.rhs stroke]
|
||||||
hookstart (XH - O) (sw -- stroke) (swItalicAdj -- Stroke)
|
hookstart (XH - O) (sw -- stroke) (swItalicAdj -- Stroke)
|
||||||
g4 (RightSB + OX) (XH - smooth)
|
g4 (RightSB + OX) (XH - smooth)
|
||||||
SNeck (stroke -- ess) (slantCoeff -- 0.02) (dist -- [SmallSDistCoeff]) (tensionCoeff -- [SmallSTensionCoeff stroke])
|
SNeck (stroke -- ess)
|
||||||
g4 (SB - OX + SOBot) (smooth) [widths stroke 0]
|
g4 (SB - OX + SOBot) (smooth) [widths stroke 0]
|
||||||
match sb
|
match sb
|
||||||
[Just SLAB-CLASSICAL] : SerifedArcEnd_LtrLhs RightSB Middle 0 stroke SHook
|
[Just SLAB-CLASSICAL] : SerifedArcEnd_LtrLhs RightSB Middle 0 stroke SHook
|
||||||
|
|
|
@ -185,6 +185,11 @@ export : define [SetupBuilders args] : begin
|
||||||
define [convertSpiroToBezier knots closed ctx] : begin
|
define [convertSpiroToBezier knots closed ctx] : begin
|
||||||
return : SpiroJs.spiroToBezierOnContext knots closed ctx CurveUtil.SPIRO_PRECISION
|
return : SpiroJs.spiroToBezierOnContext knots closed ctx CurveUtil.SPIRO_PRECISION
|
||||||
|
|
||||||
|
define [iterateNormals s closed] : begin
|
||||||
|
local knotsP2 : s.getPass2Knots closed [fallback s.contrast Contrast]
|
||||||
|
local s2 : new SpiroExpansionContext2 s.controlKnots s.gizmo
|
||||||
|
SpiroJs.spiroToArcsOnContext knotsP2 closed s2
|
||||||
|
|
||||||
define [dispiro] : let [args : {}.slice.call arguments 0] : lambda [] : begin
|
define [dispiro] : let [args : {}.slice.call arguments 0] : lambda [] : begin
|
||||||
define CLOSED true
|
define CLOSED true
|
||||||
local s : new SpiroExpansionContext1 (this.gizmo || GlobalTransform)
|
local s : new SpiroExpansionContext1 (this.gizmo || GlobalTransform)
|
||||||
|
@ -195,10 +200,8 @@ export : define [SetupBuilders args] : begin
|
||||||
if af : af.apply this args
|
if af : af.apply this args
|
||||||
|
|
||||||
SpiroJs.spiroToArcsOnContext knots closed s
|
SpiroJs.spiroToArcsOnContext knots closed s
|
||||||
local knotsP2 : s.getPass2Knots closed [fallback s.contrast Contrast]
|
iterateNormals s closed
|
||||||
# console.log knotsP2
|
iterateNormals s closed
|
||||||
local s2 : new SpiroExpansionContext2 s.controlKnots s.gizmo
|
|
||||||
SpiroJs.spiroToArcsOnContext knotsP2 closed s2
|
|
||||||
local {.lhs lhs .rhs rhs} : s.expand [fallback s.contrast Contrast]
|
local {.lhs lhs .rhs rhs} : s.expand [fallback s.contrast Contrast]
|
||||||
|
|
||||||
if closed : then
|
if closed : then
|
||||||
|
|
|
@ -139,7 +139,7 @@ export : define [calculateMetrics para] : begin
|
||||||
define PeriodRadius : PeriodSize / 2
|
define PeriodRadius : PeriodSize / 2
|
||||||
define SideJut : Jut - HalfStroke * HVContrast
|
define SideJut : Jut - HalfStroke * HVContrast
|
||||||
|
|
||||||
define [StrokeWidthBlend min max sw] : linreg 18 min 126 max [fallback sw Stroke]
|
define [StrokeWidthBlend min max sw] : linreg para.canonicalStrokeWidthMin min para.canonicalStrokeWidthMax max [fallback sw Stroke]
|
||||||
|
|
||||||
define SmoothAdjust : StrokeWidthBlend 80 144
|
define SmoothAdjust : StrokeWidthBlend 80 144
|
||||||
define [SmoothAOf smooth width] : smooth - TanSlope * SmoothAdjust / Width * width
|
define [SmoothAOf smooth width] : smooth - TanSlope * SmoothAdjust / Width * width
|
||||||
|
|
|
@ -87,8 +87,11 @@ function getBlendArg(blendArgs, style) {
|
||||||
function hiveBlend(hive, value) {
|
function hiveBlend(hive, value) {
|
||||||
if (!hive || !hive.blend || value == null) return hive;
|
if (!hive || !hive.blend || value == null) return hive;
|
||||||
|
|
||||||
const generatedHive = {};
|
|
||||||
const block = hive.blend;
|
const block = hive.blend;
|
||||||
|
delete hive.blend;
|
||||||
|
|
||||||
|
const generatedHive = { ...hive };
|
||||||
|
|
||||||
let keys = new Set();
|
let keys = new Set();
|
||||||
for (const grade in block) {
|
for (const grade in block) {
|
||||||
if (!isFinite(parseFloat(grade))) continue;
|
if (!isFinite(parseFloat(grade))) continue;
|
||||||
|
|
|
@ -1,6 +1,17 @@
|
||||||
const Transform = require("./transform");
|
|
||||||
const { linreg } = require("./utils");
|
const { linreg } = require("./utils");
|
||||||
|
|
||||||
|
class BiKnot {
|
||||||
|
constructor(type, x, y, d1, d2) {
|
||||||
|
this.type = type;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.d1 = d1;
|
||||||
|
this.d2 = d2;
|
||||||
|
this.origTangent = null;
|
||||||
|
this.proposedNormal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SpiroExpansionContext1 {
|
class SpiroExpansionContext1 {
|
||||||
constructor(gizmo) {
|
constructor(gizmo) {
|
||||||
this.gizmo = gizmo;
|
this.gizmo = gizmo;
|
||||||
|
@ -13,30 +24,22 @@ class SpiroExpansionContext1 {
|
||||||
moveTo(x, y, unimportant) {
|
moveTo(x, y, unimportant) {
|
||||||
if (unimportant) return;
|
if (unimportant) return;
|
||||||
if (!isFinite(x) || !isFinite(y)) throw new Error("NaN detected.");
|
if (!isFinite(x) || !isFinite(y)) throw new Error("NaN detected.");
|
||||||
this.controlKnots.push({
|
const tfZ = this.gizmo.apply({ x, y });
|
||||||
type: "g4",
|
this.controlKnots.push(new BiKnot("g2", tfZ.x, tfZ.y, this.defaultD1, this.defaultD2));
|
||||||
d1: this.defaultD1,
|
|
||||||
d2: this.defaultD2,
|
|
||||||
...this.gizmo.apply({ x, y })
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
arcTo(arc, x, y) {
|
arcTo(arc, x, y) {
|
||||||
if (!isFinite(x) || !isFinite(y)) throw new Error("NaN detected.");
|
if (!isFinite(x) || !isFinite(y)) throw new Error("NaN detected.");
|
||||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||||
if (!k0) throw new Error("Unreachable: lineTo called before moveTo");
|
if (!k0) throw new Error("Unreachable: lineTo called before moveTo");
|
||||||
if (k0.normalAngle == null) {
|
if (k0.origTangent == null) {
|
||||||
const tfDerive0 = this.gizmo.applyOffset({ x: arc.deriveX0, y: arc.deriveY0 });
|
k0.origTangent = this.gizmo.applyOffset({ x: arc.deriveX0, y: arc.deriveY0 });
|
||||||
k0.normalAngle = Math.PI / 2 + Math.atan2(tfDerive0.y, tfDerive0.x);
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const tfDerive1 = this.gizmo.applyOffset({ x: arc.deriveX1, y: arc.deriveY1 });
|
const tfDerive1 = this.gizmo.applyOffset({ x: arc.deriveX1, y: arc.deriveY1 });
|
||||||
this.controlKnots.push({
|
const tfZ = this.gizmo.apply({ x, y });
|
||||||
type: "g4",
|
const bz = new BiKnot("g2", tfZ.x, tfZ.y, k0.d1, k0.d2);
|
||||||
d1: k0.d1,
|
bz.origTangent = tfDerive1;
|
||||||
d2: k0.d2,
|
this.controlKnots.push(bz);
|
||||||
...this.gizmo.apply({ x, y }),
|
|
||||||
normalAngle: Math.PI / 2 + Math.atan2(tfDerive1.y, tfDerive1.x)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setWidth(l, r) {
|
setWidth(l, r) {
|
||||||
|
@ -87,8 +90,8 @@ class SpiroExpansionContext1 {
|
||||||
dx = knot.proposedNormal.x;
|
dx = knot.proposedNormal.x;
|
||||||
dy = knot.proposedNormal.y;
|
dy = knot.proposedNormal.y;
|
||||||
} else {
|
} else {
|
||||||
dx = normalX(knot.normalAngle, contrast);
|
dx = normalX(knot.origTangent, contrast);
|
||||||
dy = normalY(knot.normalAngle, contrast);
|
dy = normalY(knot.origTangent, contrast);
|
||||||
}
|
}
|
||||||
lhs[j] = {
|
lhs[j] = {
|
||||||
type: knot.type,
|
type: knot.type,
|
||||||
|
@ -155,24 +158,27 @@ class SpiroExpansionContext2 {
|
||||||
}
|
}
|
||||||
arcTo(arc, x, y) {
|
arcTo(arc, x, y) {
|
||||||
if (this.nKnotsProcessed === 1) {
|
if (this.nKnotsProcessed === 1) {
|
||||||
const angle = computeNormalAngle(this.gizmo, arc.deriveX0, arc.deriveY0);
|
const d = this.gizmo.applyOffset({ x: arc.deriveX0, y: arc.deriveY0 });
|
||||||
if (isFinite(angle)) this.controlKnots[0].normalAngle = angle;
|
if (isTangentValid(d)) this.controlKnots[0].origTangent = d;
|
||||||
else throw new Error("NaN angle detected.");
|
else throw new Error("NaN angle detected.");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const angle = computeNormalAngle(this.gizmo, arc.deriveX1, arc.deriveY1);
|
const d = this.gizmo.applyOffset({ x: arc.deriveX1, y: arc.deriveY1 });
|
||||||
if (isFinite(angle)) this.controlKnots[this.nKnotsProcessed].normalAngle = angle;
|
if (isTangentValid(d)) this.controlKnots[this.nKnotsProcessed].origTangent = d;
|
||||||
else throw new Error("NaN angle detected.");
|
else throw new Error("NaN angle detected.");
|
||||||
}
|
}
|
||||||
this.nKnotsProcessed += 1;
|
this.nKnotsProcessed += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalX(angle, contrast) {
|
function isTangentValid(d) {
|
||||||
return Math.cos(angle) * contrast;
|
return isFinite(d.x) && isFinite(d.y);
|
||||||
}
|
}
|
||||||
function normalY(angle) {
|
function normalX(tangent, contrast) {
|
||||||
return Math.sin(angle);
|
return contrast * (-tangent.y / Math.hypot(tangent.x, tangent.y));
|
||||||
|
}
|
||||||
|
function normalY(tangent) {
|
||||||
|
return tangent.x / Math.hypot(tangent.x, tangent.y);
|
||||||
}
|
}
|
||||||
function reverseKnotType(ty) {
|
function reverseKnotType(ty) {
|
||||||
return ty === "left" ? "right" : ty === "right" ? "left" : ty;
|
return ty === "left" ? "right" : ty === "right" ? "left" : ty;
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
[shapeWeight]
|
||||||
|
canonicalStrokeWidthMin = 18 # sync with 100 entry below
|
||||||
|
canonicalStrokeWidthMax = 108 # sync with 900 entry below
|
||||||
|
|
||||||
[shapeWeight.blend.400]
|
[shapeWeight.blend.400]
|
||||||
stroke = 70 # Primary stroke width
|
stroke = 70 # Primary stroke width
|
||||||
contrast = 1.11 # Stroke width contrast
|
contrast = 1.11 # Stroke width contrast
|
||||||
|
@ -89,3 +93,5 @@ vjut = 200
|
||||||
smooth = 215
|
smooth = 215
|
||||||
rbalance = 50
|
rbalance = 50
|
||||||
rbalance2 = 25
|
rbalance2 = 25
|
||||||
|
cthin = 0.75
|
||||||
|
cthinb = 0.5
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue