Summary
When kShotai = kGothic and a curve stroke (cdDrawCurve / cdDrawBezier) is rendered, the start type (a1/ta1) and end type (a2/ta2) arguments are silently ignored — the function never extends the curve endpoints to compensate for the requested termination style. Straight-line strokes (cdDrawLine) honour the same arguments correctly, so the inconsistency is contour-shape specific.
I want to flag this for visibility rather than ship a unilateral fix, because I think it is the kind of behavior that needs to align with the original kamichikoichi/kage-engine first (per your guidance in #2).
Current behavior in this repo (kurgm/kage-engine)
In src/font/gothic/cd.ts cdDrawCurveU declares two locals without ever assigning them:
function cdDrawCurveU(
font: Gothic, polygons: Polygons,
x1, y1, sx1, sy1, sx2, sy2, x2, y2,
_ta1: number, _ta2: number,
) {
// eslint-disable-next-line no-unassigned-vars
let a1: number;
// eslint-disable-next-line no-unassigned-vars
let a2: number;
let delta1 = 0;
switch (a1! % 10) { // ?????
case 2:
delta1 = font.kWidth;
break;
case 3:
delta1 = font.kWidth * font.kKakato;
break;
}
// ...same pattern for a2/delta2...
}
a1! / a2! are non-null assertions on values that never get a value — they are undefined at runtime. undefined % 10 evaluates to NaN, so the switch never matches any case, and delta1 / delta2 always stay 0. Effect: the curve endpoints are never extended.
cdDrawLine in the same file does it correctly — there a1 and a2 are assigned to either ta1/ta2 or their swapped counterparts before the same switch. The leading ????? comments in cdDrawCurveU and the eslint-disable no-unassigned-vars directives suggest the implementer knew something was off but preserved the pattern intentionally.
Why I think this matches the original engine
I traced the equivalent code in kamichikoichi/kage-engine/kagecd.js and the same dead-code path is present:
function cdDrawCurveU(kage, polygons, x1, y1, sx1, sy1, sx2, sy2, x2, y2, ta1, ta2){
var rad, t;
// ...
var a1, a2, opt1, opt2, opt3, opt4;
if(kage.kShotai == kage.kMincho){ // mincho
a1 = ta1 % 1000;
a2 = ta2 % 100;
// ...full mincho-only assignment + handling, ~600 lines...
}
else{ //gothic
if(a1 % 10 == 2){ /* ... */ } // a1 is `var`-hoisted, so undefined here
if(a1 % 10 == 3){ /* ... */ }
if(a2 % 10 == 2){ /* ... */ }
// ...
}
}
So the TS port is faithful to the original — the assignments only exist on the Mincho branch, and the Gothic branch reads the hoisted-but-uninitialized a1/a2. As far as I can tell, this dead code was preserved intentionally during the TS port to match the original.
Why this is visible in practice
It primarily affects hiragana and katakana, where every stroke is a curve and most strokes have meaningful start/end types (e.g. 2:7:8:...):
- Mincho honours
a2 = 7 / a3 = 8 and produces the expected うろこ・はね・とめ decorations, giving each kana its characteristic style.
- Gothic falls back to a uniform-width curve with no endpoint extension, so kana glyphs look more like a "mincho skeleton with decorations stripped" than a real Gothic typeface.
(For kanji the visual gap is smaller because most strokes are straight lines, where cdDrawLine already handles a1/a2 correctly.)
We hit this while building a free-license Gothic font from GlyphWiki dumps. The end-type extension Gothic produces today is "good enough" for most kanji but visibly weak on kana.
Possible directions
I'd appreciate your guidance on which (if any) of these makes sense:
-
Treat as upstream-locked. If you'd like to keep the TS port aligned with kamichikoichi/kage-engine exactly, the right path is for the Gothic branch to be fixed in the original first. I'm happy to file a parallel issue there if you point me to the right channel.
-
Opt-in Gothic improvement here. Add an off-by-default font parameter (e.g. font.kGothicHonorEndTypes, defaulting to false) that, when enabled, populates a1/a2 from ta1/ta2 and runs the existing extension logic. The default keeps original-compatible output; opting in gives downstream font generators (TTF / SVG with non-zero fill) a better-shaped Gothic without forking the engine. I'd be happy to send this as a PR.
-
Document as known limitation. Add a sentence to the README / Gothic-related TSDoc clarifying that a1/a2 are intentionally ignored on Gothic curves for original-engine parity, so future readers don't waste time tracing the dead code.
I'm open to whichever fits the project's direction. Mostly I wanted to surface this so that anyone hitting the same kana-quality wall can find prior art.
Refs #2
Summary
When
kShotai = kGothicand a curve stroke (cdDrawCurve/cdDrawBezier) is rendered, the start type (a1/ta1) and end type (a2/ta2) arguments are silently ignored — the function never extends the curve endpoints to compensate for the requested termination style. Straight-line strokes (cdDrawLine) honour the same arguments correctly, so the inconsistency is contour-shape specific.I want to flag this for visibility rather than ship a unilateral fix, because I think it is the kind of behavior that needs to align with the original
kamichikoichi/kage-enginefirst (per your guidance in #2).Current behavior in this repo (
kurgm/kage-engine)In
src/font/gothic/cd.tscdDrawCurveUdeclares two locals without ever assigning them:a1!/a2!are non-null assertions on values that never get a value — they areundefinedat runtime.undefined % 10evaluates toNaN, so the switch never matches any case, anddelta1/delta2always stay0. Effect: the curve endpoints are never extended.cdDrawLinein the same file does it correctly — therea1anda2are assigned to eitherta1/ta2or their swapped counterparts before the same switch. The leading?????comments incdDrawCurveUand theeslint-disable no-unassigned-varsdirectives suggest the implementer knew something was off but preserved the pattern intentionally.Why I think this matches the original engine
I traced the equivalent code in
kamichikoichi/kage-engine/kagecd.jsand the same dead-code path is present:So the TS port is faithful to the original — the assignments only exist on the Mincho branch, and the Gothic branch reads the hoisted-but-uninitialized
a1/a2. As far as I can tell, this dead code was preserved intentionally during the TS port to match the original.Why this is visible in practice
It primarily affects hiragana and katakana, where every stroke is a curve and most strokes have meaningful start/end types (e.g.
2:7:8:...):a2 = 7/a3 = 8and produces the expected うろこ・はね・とめ decorations, giving each kana its characteristic style.(For kanji the visual gap is smaller because most strokes are straight lines, where
cdDrawLinealready handlesa1/a2correctly.)We hit this while building a free-license Gothic font from GlyphWiki dumps. The end-type extension Gothic produces today is "good enough" for most kanji but visibly weak on kana.
Possible directions
I'd appreciate your guidance on which (if any) of these makes sense:
Treat as upstream-locked. If you'd like to keep the TS port aligned with
kamichikoichi/kage-engineexactly, the right path is for the Gothic branch to be fixed in the original first. I'm happy to file a parallel issue there if you point me to the right channel.Opt-in Gothic improvement here. Add an off-by-default font parameter (e.g.
font.kGothicHonorEndTypes, defaulting tofalse) that, when enabled, populatesa1/a2fromta1/ta2and runs the existing extension logic. The default keeps original-compatible output; opting in gives downstream font generators (TTF / SVG with non-zero fill) a better-shaped Gothic without forking the engine. I'd be happy to send this as a PR.Document as known limitation. Add a sentence to the README / Gothic-related TSDoc clarifying that
a1/a2are intentionally ignored on Gothic curves for original-engine parity, so future readers don't waste time tracing the dead code.I'm open to whichever fits the project's direction. Mostly I wanted to surface this so that anyone hitting the same kana-quality wall can find prior art.
Refs #2